1. 为什么我建议从海龟作图切入Python基础语法
1.1 海龟作图到底解决了初学者的什么问题
很多人学完变量、循环、条件判断之后会卡在一个很尴尬的位置:语法都认识,但写不出东西。屏幕上只有黑底白字的print输出,脑子里没有画面,练着练着就没劲了。Python基础语法第二阶段的扩展模块里,turtle海龟作图恰好是填这个坑的最佳选择。它把抽象的坐标、角度、循环次数变成看得见的线条,你写一行forward(100),画布上立刻多出一段100像素的直线;你把角度算错,图立刻歪给你看。这种即时反馈对初学者特别重要。
我在带新手的时候有个很深的体会:海龟作图本质上是一套“把数学语言翻译成图形语言”的训练器。它不追求工程能力,而是让你在写代码的过程中反复练习三件事——顺序执行的直觉、循环结构的复用、函数封装的边界。这三件事恰好是后面写数据处理、写脚本、写小工具的地基。换句话说,turtle不是玩具,它是一块低成本的练习场,画错擦掉重来,代价几乎为零。
这篇文章面向两类人:一类是刚学完基础语法、想找个能出成果的小项目练手的同学;另一类是想把turtle拿去给孩子或学生做入门教学的人。下面我会从画布坐标系讲到循环函数封装,再给一个能直接跑起来的完整案例,最后把我踩过的坑整理成速查表。所有代码都可以直接复制到本地跑,不需要额外装库,turtle是Python标准库自带的。
1.2 环境准备与第一次落笔的完整流程
先说环境。turtle属于标准库,不需要pip install,只要你的Python版本是3.x就能直接用。唯一的差别在于不同系统自带的图形界面组件略有不同,Windows和macOS上基本开箱即用,Linux桌面环境下如果报出图形界面相关的错误,通常是缺了图形组件,装一下系统层面的图形支持就好,跟Python本身无关。
第一次落笔我建议只写四行,把最核心的动作跑通:
import turtle t = turtle.Turtle() t.forward(100) turtle.done()这四行分别对应四个动作:把turtle模块请进来、创建一只画笔对象、让它往前走100像素、把窗口留住。很多新手漏掉最后一行,结果是窗口一闪就没了,然后怀疑自己代码写错了。turtle.done()的作用是让程序进入事件循环,等待用户关闭窗口,没有它,脚本执行完就退出,绘图窗口自然跟着消失。
这里有个细节值得说清楚:turtle.Turtle()是创建一个画笔对象,而不是直接操作模块级的函数。模块级也提供turtle.forward(100)这种写法,内部其实是操作一只默认画笔。我强烈建议从一开始就用对象写法,原因是当你需要两只、三只画笔同时画不同颜色的时候,对象写法能够清晰地区分每一只画笔的状态,模块级写法会让你陷入“到底改的是哪只笔”的混乱。这个习惯越早养成越好。
注意:代码文件不要命名为
turtle.py,否则import turtle会导入你自己这个文件,报出一堆“找不到属性”的错误。这是新手最常见的翻车原因之一,而且报错信息很有迷惑性。
2. 坐标系与画笔控制:把画布当成一张真实图纸
2.1 画布、坐标系与角度方向的三层认知
用海龟作图,脑子里必须建立三层结构:画布、坐标、朝向。画布是那块白色矩形区域,默认大小是400×300左右,中心点是坐标原点(0,0)。坐标是平面直角坐标系,向右是x正方向,向上是y正方向。朝向是海龟自己的“头”指向哪里,初始朝向是向右,也就是数学上的0度。
这三层关系不清楚,画图就会变成瞎试。我见过不少人画五角星画了半小时,问题不在循环次数,而在于没搞明白right(144)里的144度是怎么来的。角度这件事我建议用一张纸来辅助理解:海龟站在纸上,面朝右,你让它右转90度,它就面朝下;再右转90度,面朝左。转的是海龟的身体,不是坐标轴。这个认知一旦建立,所有角度计算都能自己推。
画布本身也可以调。常用的两个方法是:
turtle.setup(width=800, height=600, startx=0, starty=0) turtle.title("我的第一幅海龟画")setup的三个参数里,宽高单位是像素,起始位置是窗口左上角在屏幕上的位置,不写就用默认居中。title改窗口标题,做作品展示的时候很有用,属于成本极低但效果明显的细节。
角度方向还有一个容易被忽略的点:left()和right()是相对转向,setheading()是绝对朝向。相对转向适合画多边形,因为你只需要关心“每次转多少”;绝对朝向适合画特定方位的图形,比如让海龟始终朝上。两者混用时一定要想清楚当前朝向是多少,否则会出现“明明角度算对了,画出来却歪了”的情况。我的习惯是画规则图形一律用相对转向,画位置固定的装饰元素才用setheading。
2.2 画笔属性与移动指令的完整参数表
海龟的动作指令不多,但每个都有几个参数,把参数摸清楚比死记语法有用得多。下面这张表是我平时贴在显示器边上的版本:
| 指令 | 作用 | 常用参数说明 |
|---|---|---|
forward(d)/fd(d) | 沿当前朝向前进 | d为像素距离,负数表示后退 |
backward(d)/bk(d) | 后退 | 等价于负数前进 |
right(a)/rt(a) | 顺时针转 | a为角度,可为小数 |
left(a)/lt(a) | 逆时针转 | 同上 |
goto(x, y) | 移动到绝对坐标 | 移动过程默认画线 |
setx(x)/sety(y) | 只改一个坐标 | 另一个坐标保持不变 |
setheading(a)/seth(a) | 设定绝对朝向 | 0为向右,90为上 |
home() | 回到原点并朝右 | 移动过程默认画线 |
circle(r) | 画圆 | r为半径,正数在左侧画 |
dot(size, color) | 画实心圆点 | 不移动位置 |
circle()是很多人第一个栽跟头的地方。它不是“以当前位置为圆心画圆”,而是“以当前位置为圆周上的一点,沿当前朝向的左侧画圆”。也就是说半径为正时,圆心在你左手边;半径为负时,圆心在右手边。这个设计初看反直觉,但用熟了会发现它很省事——想画一整圈花瓣,只需要不停circle(r, 60)加转向,不用算圆心坐标。
画笔属性方面,pensize(n)改粗细,pencolor(c)改线条颜色,fillcolor(c)改填充色,hideturtle()藏起海龟图标,shape("turtle")换个造型。线条粗细和颜色建议在画之前统一设一次,而不是每画一笔设一次,因为每次设置都会产生一次状态变更,虽然不影响结果,但代码会变得又长又乱。真正需要中途改颜色的场景,通常是画分区图形或者做渐变效果,那种情况下再逐段设置。
2.3 抬笔落笔与多图形拼接的实操
penup()和pendown()这一对指令,是海龟作图从“一笔画”走向“多笔画”的分水岭。抬笔后移动不留下痕迹,落笔后移动才画线。听起来简单,但实际作图时,图形之间的空白间隔、装饰元素的位置摆放,全靠这一对指令控制。
举个最常见的场景:在画布上并排画三个正方形,间隔50像素。思路是每画完一个正方形,抬笔往前走“边长+间隔”的距离,再落笔画下一个。代码骨架长这样:
import turtle t = turtle.Turtle() t.speed(5) side = 80 gap = 50 for i in range(3): for _ in range(4): t.forward(side) t.right(90) t.penup() t.forward(side + gap) t.pendown() turtle.done()这段代码里有个细节值得单独拎出来:内层循环用_作为变量名,表示这个循环变量我不用。这是一个很常见的写法习惯,读代码的人一看就知道“这里只关心循环次数”。外层循环结束后抬笔前移,注意前移的距离是side + gap而不是gap,因为画完一个正方形后海龟停在起点的正上方那条边的终点,必须把整条边长补上才能到达下一个正方形的起点。这种“当前位置在哪”的推导,是海龟作图最核心的思维训练,比背语法有用得多。
抬笔期间如果调用了goto(),海龟会直接“瞬移”过去,中间不画线。这个特性在做复杂图形拼接时非常好用,比如画一面由多个小方块组成的棋盘,用goto直接定位到每个格子的左上角,比一路算相对位移省心得多。我的经验是:相对移动适合规则重复的结构,绝对定位适合坐标已知的布局。两者按场景选,不要死磕一种。
3. 循环与函数:让海龟作图从画一笔到画一类
3.1 用for循环替代重复劳动
海龟作图最能体现循环价值的地方,就是画正多边形。一个正方形要写四组“前进、右转90度”,一个正十二边形就要写十二组,手写代码不仅长,还容易漏。用for循环一包,立刻清爽:
import turtle t = turtle.Turtle() n = 8 side = 60 for _ in range(n): t.forward(side) t.right(360 / n) turtle.done()这里的关键不是循环语法,而是360 / n这个表达式的来历。海龟画完一条边后要转弯,转的角度必须是外角,而正n边形的外角和恒为360度,所以每个外角就是360/n。很多人第一次会写成内角,画出来是个自交的星形,然后一脸茫然。内角和外角的区别,是海龟作图里第一个真正的数学门槛:内角是图形内部两条边之间的夹角,外角是你在顶点处需要转过的角度,两者互补,加起来180度。想清楚这一点,正三角形转120度、正方形转90度、正六边形转60度就全都顺了。
循环变量在这里还有个实际用途:如果你想让每条边的颜色不一样,可以直接用i去索引一个颜色列表。不过要注意,颜色列表的长度最好和边数匹配,或者用取余运算i % len(colors)做循环取色,否则会越界报错。这类小技巧在后面的实战案例里我会再用到。
3.2 把图形封装成函数
画出第一个正多边形之后,下一步就该把它封装成函数了。理由很实在:你不可能只画一个图形,一旦要画多个,复制粘贴的代码就会迅速膨胀,改一个参数要改好几处,出错概率直线上升。封装成函数之后,画什么图形、画多大、画在哪,全部变成参数,调用方只关心“我要一个什么样的图形”。
import turtle def polygon(t, n, side): """让画笔t画一个正n边形,边长为side""" for _ in range(n): t.forward(side) t.right(360 / n)这个函数有三个参数:画笔对象、边数、边长。把画笔作为参数传进来,而不是在函数内部新建,是为了让同一个函数能服务多只画笔,也让调用方掌控画笔的状态(颜色、粗细、位置)。这是一个很小的设计决策,但影响很大。如果函数内部自己Turtle(),那每调用一次就多一只笔,画布上会乱成一团,而且你没法在调用前设置颜色。
封装之后,画一排不同边数的多边形就是几行调用的事:
t = turtle.Turtle() t.speed(0) for n in range(3, 9): polygon(t, n, 50) t.penup() t.forward(80) t.pendown() turtle.done()提示:函数里的
360 / n在Python 3里是浮点除法,结果是小数,right()接受小数角度,没问题。但如果你的本意是整除,记得写//,别在不该省的地方省。
3.3 参数化设计:把图形变成可配置的积木
函数写多了会发现一个规律:好的绘图函数,参数应该描述“要什么”,而不是“怎么做”。比如polygon(t, n, side)描述的是“一个正n边形,边长side”,调用方不需要知道内部是前进加右转。这种思路再往前走一步,就能做出组合图形。
举个实际例子,画一朵由若干花瓣组成的花。花瓣可以用两段圆弧拼成,而整朵花是花瓣绕中心旋转复制。拆解下来是两个函数:一个画单片花瓣,一个负责旋转复制。
import turtle def petal(t, radius, angle): """画一片花瓣:两段圆弧""" t.circle(radius, angle) t.left(180 - angle) t.circle(radius, angle) t.left(180 - angle) def flower(t, petals, radius, angle): """画一朵花:petals片花瓣绕中心旋转""" for _ in range(petals): petal(t, radius, angle) t.left(360 / petals)这段代码里petal的两个left(180 - angle)是用来把朝向掰回来的,不写的话花瓣会越画越歪。这类“画完一段要回正”的操作,在海龟作图里非常普遍,本质上是状态管理:每画完一个子图形,要让画笔回到一个已知的、可预期的状态,下一个子图形才能正确接上。我个人的习惯是,每个绘图函数结束前都检查一遍画笔的朝向和位置,如果调用方需要,就在文档字符串里写清楚“函数返回时画笔朝向为X”。
参数化还有一个好处是可测试。你可以先用小参数快速验证形状对不对,比如flower(t, 5, 40, 60),确认没问题再放大到flower(t, 12, 120, 45)出成品。这种“小步快跑”的调试方式,比一次性写一大段然后对着歪图猜哪里错了高效得多。
4. 填充、颜色与事件:让作品有层次和交互
4.1 颜色体系与填充的正确姿势
turtle支持三种颜色写法:颜色名字符串(如"red"、"steelblue")、十六进制字符串(如"#FF5733")、RGB元组。用RGB元组之前必须调用turtle.colormode(255),否则默认是0到1的小数范围,你写(255, 0, 0)会被当成超范围值处理,颜色可能不对甚至报错。这个坑我踩过不止一次,尤其是在抄别人代码的时候,对方设了colormode,你没设,结果颜色全乱。
填充的流程是三段式:begin_fill()开始记录要填充的区域,画完闭合图形,end_fill()执行填充。中间画出的路径会被自动闭合并填色。
import turtle t = turtle.Turtle() t.pensize(2) t.pencolor("#2C3E50") t.fillcolor("#F39C12") t.begin_fill() for _ in range(4): t.forward(120) t.right(90) t.end_fill() turtle.done()这里有个必须强调的细节:begin_fill()和end_fill()必须成对出现,且中间画的路径要尽量闭合。如果路径没闭合,turtle会自动从终点连一条直线回起点再填充,视觉上可能没问题,但如果你的路径是复杂曲线,自动闭合的那条线会很明显地横穿图形,看起来像画错了。解决办法是在end_fill()之前手动goto回起点,或者确保循环画完后正好回到出发点。
还有一个容易被忽略的属性:填充色和线条色是分开的。pencolor管边框,fillcolor管内部,两者可以不同。想让图形看起来有层次,通常是深色边框配浅色填充,或者反过来。纯色填充看起来平,如果想让作品有点立体感,可以用同色系的两个深浅色,在图形内部再叠一个小一号的图形,做出类似高光的效果。这个技巧在后面实战案例里会用到。
4.2 文字、随机数与事件绑定
write()能在画布上写字,参数不算复杂但顺序容易记混:
t.write("Hello", move=False, align="center", font=("Arial", 16, "bold"))move为True时,写字后海龟会移动到文字右下角;为False则原地不动。align控制对齐方式,可选"left"、"center"、"right"。font是一个三元组:字体名、字号、字形。字号单位是像素而不是磅,所以同样的数字在不同系统上视觉大小会有差异,做跨平台作品时别把字号写死得太极限。
随机数配合海龟作图,能做出很多有意思的效果。比如随机颜色的星星、随机位置的圆点、随机长度的线段。用法很简单,import random之后用random.randint(a, b)取整数,random.choice(列表)取随机元素。但要注意一个实际问题:随机不等于好看。纯随机的作品往往显得杂乱,我的做法是先随机构图,再手动调几个关键参数(比如限定颜色范围、限定坐标区域),让随机性落在可控区间内。
事件绑定是turtle里比较有意思的部分,能让作品从“静态图”变成“可交互的小程序”。核心是三个函数:onclick(func)绑定鼠标点击,onkey(func, key)绑定键盘按键,listen()让窗口开始监听事件。
import turtle t = turtle.Turtle() t.shape("turtle") t.pensize(3) def draw_circle(x, y): t.penup() t.goto(x, y) t.pendown() t.circle(30) turtle.onscreenclick(draw_circle) turtle.done()这段代码的效果是:在画布上任意位置点一下,就在那个位置画一个半径30的圆。onscreenclick的回调函数会收到点击位置的x、y坐标,直接拿来用就行。这种“点击即绘制”的交互,用来做小游戏或者教学演示都很合适。不过要注意,事件绑定必须在done()之前完成,而且回调函数里不要写死循环,否则界面会卡住。
4.3 速度、追踪与动画控制
t.speed(0)是最快,speed(1)到speed(10)依次变快,不设的话默认是中速。这个参数在开发阶段和展示阶段的用法完全不同:开发时我建议用speed(0)或者干脆关掉动画,快速看到结果;展示时用中速甚至慢速,让观众看清绘制过程,反而更有观赏性。
比speed更彻底的是tracer(0),它会直接关闭自动重绘,画面不再实时刷新,必须手动调用turtle.update()才显示。这个组合在画复杂图形时能把渲染时间从几十秒压到一两秒:
turtle.tracer(0) # 这里放大量绘图代码 turtle.update()原理很简单:每次绘图指令都会触发一次屏幕刷新,图形越复杂刷新次数越多,累积起来就是肉眼可见的卡顿。关掉自动刷新后,所有绘制在内存里完成,最后统一刷一次,速度差异非常明显。代价是绘制过程中你看不到中间状态,所以调试阶段别开tracer(0),等图形确认无误了再加。
还有一个容易被忽略的控制项是hideturtle()。海龟图标在绘制过程中会遮挡线条,尤其是画密集图形的时候,看着很乱。藏起来之后画面干净很多。如果想让海龟出现,用showturtle()。这两个指令不影响绘制结果,纯粹是视觉层面的开关,属于“知道了就能让作品更精致”的细节。
5. 完整实战案例:从草图到成品的分步实现
5.1 案例设计说明与模块划分
光讲指令容易散,我用一个完整的案例把前面的东西串起来。目标是画一幅“同心彩色多边形花环”:中心是一个实心圆,外围环绕一圈正多边形,每个多边形颜色不同,整体有旋转的层次感。
拆解一下需求,可以分成四个模块:画布初始化、中心圆、外围多边形、整体布局。每个模块对应一个函数,函数之间通过参数传递画笔对象和位置信息。这种拆法不是为了炫技,而是为了每一块都能单独调试——圆画歪了只改圆的函数,多边形颜色不对只改多边形部分,不会牵一发动全身。
先定参数:画布800×600,中心圆半径40,外围放12个正多边形,每个边长45,多边形分布在半径180的圆环上。12这个数字不是随便选的,因为它能整除360,每个多边形间隔30度,布局均匀。如果用11个,间隔就是32.72度,容易出现累积误差导致的轻微错位。选能被360整除的数字,是环形布局的一个实用经验。
5.2 逐步实现与关键代码
第一步,初始化画布和画笔,把公共状态设好:
import turtle turtle.setup(800, 600) turtle.title("同心彩色多边形花环") turtle.bgcolor("#FDFEFE") turtle.colormode(255) t = turtle.Turtle() t.speed(0) t.hideturtle() t.pensize(2)bgcolor设成浅灰白,比纯白柔和,作品看着不刺眼。colormode(255)放在最前面,后面所有RGB颜色都按0到255写,统一标准。
第二步,画中心实心圆:
def draw_center(t, radius, color): t.penup() t.goto(0, -radius) t.pendown() t.setheading(0) t.fillcolor(color) t.pencolor(color) t.begin_fill() t.circle(radius) t.end_fill()这里goto(0, -radius)是关键。circle(r)是以当前位置为圆周起点画的,想让圆心落在原点,起点必须在(0, -r)。这个推导过程值得自己动手在纸上画一遍,比死记结论扎实。setheading(0)确保朝向是向右的,因为circle的绘制方向依赖当前朝向,如果之前转过角度,圆心位置会跑偏。
第三步,画单个正多边形:
def draw_polygon(t, n, side, color): t.fillcolor(color) t.pencolor("#34495E") t.begin_fill() for _ in range(n): t.forward(side) t.right(360 / n) t.end_fill()注意这里pencolor统一用深灰色而不是和填充色一致,边框和填充形成对比,图形更清晰。这是个小细节,但对比度对视觉效果的影响很大。
第四步,把多边形摆到环上。核心是用三角函数算坐标:
import math def draw_ring(t, count, ring_radius, n, side): colors = ["#E74C3C", "#E67E22", "#F1C40F", "#2ECC71", "#1ABC9C", "#3498DB", "#9B59B6", "#E84393"] for i in range(count): angle = 360 / count * i rad = math.radians(angle) x = ring_radius * math.cos(rad) y = ring_radius * math.sin(rad) t.penup() t.goto(x, y) t.setheading(angle + 90) t.pendown() draw_polygon(t, n, side, colors[i % len(colors)])坐标计算用的是标准的极坐标转直角坐标:x = r·cosθ,y = r·sinθ。角度要先转成弧度,因为math.cos和math.sin接收的是弧度。setheading(angle + 90)让每个多边形都朝向圆心外侧,视觉上更整齐;如果不设朝向,所有多边形都朝右,看起来会很乱。
颜色列表写了8个,用i % len(colors)取余循环使用,保证12个多边形不会越界。这也是前面提到的取余技巧的实际应用。
第五步,组装:
draw_center(t, 40, "#F39C12") draw_ring(t, 12, 180, 6, 45) turtle.done()完整跑下来,得到的就是一个中心橙色实心圆、外围12个六边形环绕的花环。如果想调整密度,改count和ring_radius就行;想换风格,改颜色列表。参数化带来的好处在这一步体现得很明显:改一个数字就能出一版新作品。
5.3 运行结果与调优思路
第一次跑完,大概率会遇到两个问题。第一个是图形太挤或者太散,这是ring_radius和side的比例没调好。经验值是多边形的外接圆直径大约等于side × 1.1(正六边形的情况),环上12个点的间距是2πr/12,要让多边形之间留出空隙,ring_radius至少得是边长的3倍以上。我用的180配45,比例是4比1,画出来疏密刚好。
第二个问题是颜色搭配显得土。这个跟技术无关,纯粹是审美问题。我的做法是把颜色控制在同一个色系里,用明度差异做层次,而不是赤橙黄绿青蓝紫全上。比如全用蓝色系,从深蓝到浅蓝排开,整体感会强很多。如果实在拿不准,就去搜“配色方案”找现成的十六进制色值,别自己硬调。
提示:复杂图形建议先用小参数试跑,比如
count=4、side=20,确认坐标和朝向都对,再放大到正式参数。直接上大参数,一旦出错很难定位是哪个环节的问题。
6. 常见问题与排查技巧实录
6.1 常见报错与现象速查表
海龟作图的问题大多不是语法错误,而是“画出来不对”这种逻辑问题。下面这张表是我这些年攒下来的高频问题清单,按现象查原因,比一行行看代码快得多:
| 现象 | 最可能的原因 | 解决办法 |
|---|---|---|
| 窗口一闪而过 | 缺少turtle.done()或mainloop() | 在脚本最后加上 |
| 画出来的图形歪了 | 内外角搞混,或中途朝向变了没复位 | 检查360/n,必要时加setheading |
| 填充色没生效 | begin_fill和end_fill没配对 | 检查是否成对,路径是否闭合 |
| 颜色报错 | RGB元组超范围,没设colormode(255) | 在开头加colormode(255) |
| 画笔跑到屏幕外 | 坐标算错或长距离forward | 用goto回原点,或调整画布setup |
| 图形画得很慢 | 没设速度,或用了tracer没update | 设speed(0),或tracer(0)配update() |
| 点击事件没反应 | 缺少listen()或绑定在done()之后 | 调整顺序 |
| 导入报错找不到属性 | 文件名叫turtle.py,导入了自己 | 重命名文件 |
这张表里,我觉得最值得展开的是“窗口一闪而过”和“画得慢”这两个,因为它们最容易被误判成代码错误。窗口闪退的本质是程序执行完毕退出,跟图形库没关系,任何脚本都是这个行为;画得慢的问题在复杂图形上尤其明显,很多人以为是电脑性能不行,其实是没关自动重绘。
6.2 出图慢、卡死与窗口异常的排查思路
画复杂图形卡顿,排查顺序我一般是这样的:先看有没有tracer(0),没有的话加上,这一步能解决八成性能问题;再看循环次数是不是过大,比如画一万条线,那不管怎么优化都会慢,得从算法层面减少绘制指令;最后看有没有在循环里反复设置颜色、粗细这些属性,能提到循环外的就提出来。
有个具体的数字可以参考:普通配置下,不关自动重绘,画一千条短线段大概要几秒到十几秒;关掉之后基本是瞬间完成。这个差距在开发阶段特别影响效率,因为你会反复运行调试,每次省十几秒,一天下来就是几十分钟。
窗口卡死不响应,常见原因是把耗时操作放在了事件回调里。比如点击回调中画了一万个图形,界面就会在这段时间内完全卡住。解决办法是把复杂绘制拆成小段,或者用ontimer定时执行,避免长时间占用主循环。turtle本质上是单线程的,所有绘制和事件处理都在一个循环里跑,这个特性决定了它不适合做重计算,只适合做轻量交互。
还有一种比较隐蔽的问题:多只画笔同时存在时,画面出现奇怪的交叉线条。原因通常是某只笔在penup()状态下被goto了,但另一只笔忘了抬笔,移动时留下了痕迹。排查方法是给每只笔设不同的颜色,一眼就能看出是哪只在乱画。用颜色区分调试对象这个小技巧,在多画笔场景下特别管用。
6.3 教学与自学中的避坑心得
带新手画海龟图这些年,我总结出几条不太写进教材但很实用的经验。
第一条,先画图再写代码。让学习者在纸上把要画的图形拆成基本动作,标出每一步的方向和角度,然后再翻译成代码。直接对着屏幕想角度,很容易越想越乱。纸上画一遍,角度关系一目了然,代码只是转录工作。
第二条,每加一个功能就运行一次。新手常犯的错误是一次写五十行然后一起运行,出错之后完全不知道是哪一步的问题。海龟作图的优势就是可以逐步验证,画完一条边运行一次,画完一个图形运行一次,错误永远只可能出现在最后加的那几行里。
第三条,别怕画得丑。海龟作图的价值在于练习语法和逻辑,不在于产出美术作品。第一幅作品歪歪扭扭很正常,重要的是把“循环控制次数”“函数封装逻辑”“坐标定位”这些概念用身体记住了。等这些变成肌肉记忆,后面学数据可视化、学图形界面,都会轻松很多。
第四条,把作品保存下来。turtle支持导出为图片,虽然需要借助额外的截图或保存功能,但把每个阶段的成果留档,过一段时间回头看,能明显看到进步。这种正向反馈对坚持学习很重要,比任何教程都管用。
我个人在实际操作中的体会是,海龟作图最难的不是记住那些指令,而是建立“当前状态”的意识——画笔在哪、朝哪、笔是抬着还是落着、填充开没开。这四件事随时清楚,画什么图形都只是把想法翻译成指令的过程;这四件事迷糊,就会陷入反复试错。所以每写几行,我都会在脑子里过一遍画笔的状态,这个习惯比任何调试技巧都省时间。