给零基础的人挑第一个写代码的地方,我踩过一个很典型的坑:直接把人按到专业IDE面前,装解释器、配环境变量、新建工程,折腾四十分钟,屏幕上只出来一行 hello world,热情当场熄火。后来我把入口换成了编程猫的海龟编辑器——画布上有一只小海龟,你敲一行代码它动一下,改个数字图案立刻变形,这种"所见即所得"的反馈闭环对初学者太关键了。它本质是一个内置了 Python 运行环境的编辑器,同时支持图形化积木和纯代码两种写法,还能把积木自动翻译成 Python,做的是"从拖拽过渡到手写代码"这件事。适合的读者范围其实比想象中宽:完全没碰过编程的小学生、想给孩子找入门工具的家长、需要带班的少儿编程老师,甚至是习惯用其他语言、想找个轻量环境快速验证图形算法的人。下面我按自己实际带人和自己折腾的顺序,把安装、界面、核心API、一个完整的"99乘法表"案例、循环与递归的进阶路径,以及我踩过的那些坑,一条条摊开讲。
1. 海龟编辑器凭什么适合当Python的第一站
1.1 初学阶段最贵的成本是反馈延迟
学编程最劝退的不是语法难,是"我敲的这行字到底干了什么"这件事长时间得不到回答。传统入门的路径是:写 print、跑控制台、看一行文字,然后学变量、学循环,中间可能连续两周都面对黑底白字的终端。这个阶段抽象概念密度极高,变量、列表、循环嵌套全堆在一起,学习者脑子里没有画面,只能靠死记。
turtle 这套东西的价值就在这儿——它把"变量"变成"海龟移动的距离",把"循环"变成"重复画出来的边",把"函数参数"变成"同一个图案的尺寸变化"。我在带人的时候有个很明显的观察:同样讲 for 循环,在纯控制台环境里,学员要花二三十分钟才能理解range(4)是 0 到 3;换成画正方形,for i in range(4): t.forward(100); t.right(90)这么四行,五分钟他就自己发现把 4 改成 6 会出来一个六边形。这个"自己发现"的过程,是任何讲解都替代不了的。
海龟编辑器在这条链路上又省了一步:它是客户端软件,装完就能跑,不用单独装 Python、不用配环境变量、不用管 pip 装在哪个目录。对第一次接触编程的人来说,把"配置环境"这一关直接砍掉,等于把失败率砍掉一大半。
1.2 和几种常见入门环境摆在一起看
我把手上带过的几种环境整理成了一张表,不是要说谁好谁坏,而是让你清楚各自适用的阶段。需要说明的是,海龟编辑器不同版本的界面和内置库列表会有差异,下面的对比基于我实际用过的版本。
| 环境 | 上手门槛 | 反馈形式 | 强项 | 短板 |
|---|---|---|---|---|
| 海龟编辑器 | 低,装完即用 | 图形+文字双向 | 积木/代码双模式,即时可见,适合从零过渡 | 工程化能力弱,大型项目不适用 |
| 官方 IDLE | 低 | 纯文字 | 随 Python 自带,零安装 | 无图形,调试体验原始 |
| 在线代码平台 | 极低 | 纯文字/简易图形 | 打开浏览器就能写 | 依赖网络,文件管理受限 |
| 专业编辑器(如 VS Code) | 中高 | 纯文字 | 插件生态强,能写真实项目 | 环境配置劝退新手 |
从表里能看出来,海龟编辑器的定位非常明确:它不打算做一生的主力工具,它的任务是把人从"完全不会"送到"能自己写出一百行代码、能看懂报错"。这个区间它几乎是效率最高的选择之一。我一般跟学员讲:把它当成学自行车时的辅助轮,不是终点,但没它你可能连车都上不去。
1.3 什么阶段该考虑换工具
这话我得说在前面,免得有人一直用下去。如果你已经能熟练写函数、会用列表和字典、能读懂大部分报错信息,并且想做的事变成了下面这几类,那就可以准备搬家了:
- 要处理文件和目录,读写 CSV、批量重命名文件;
- 要写网络请求、做个小型网站或者桌面窗口程序;
- 项目代码超过三五百行,需要拆成多个文件互相导入;
- 需要用版本管理工具记录修改。
这几类需求在轻量教学环境里做起来会比较别扭。反过来,只要你还在"循环怎么嵌套、函数参数怎么传、递归怎么想"这个阶段,海龟编辑器的即时图形反馈就是最大的助力,没必要急着换。
2. 从下载到画出第一条线:环境准备与界面拆解
2.1 安装环节几个容易被忽略的细节
安装包从官方渠道拿,Windows 和 macOS 一般都有对应版本,跟着引导点下去就行,没什么复杂选项。但我实测下来有几个细节值得提前注意:
- 安装路径尽量用纯英文,不要带空格和中文字符。绝大多数时候没事,但当你想装第三方绘图库、或者读写素材文件时,中文路径偶尔会让某些库找不到文件,报错信息还特别隐晦。一开始就装到
D:\Codemao这种简单路径下,能省掉后面很多无意义的排查。 - 首次启动可能会下载运行环境或内置资源,这段时间界面是空白的,别急着关掉重装,等进度走完就好。
- 部分安全软件可能对安装或联网行为弹提示,选择允许即可,这类教学软件本身没有问题。
- 屏幕缩放比例。如果系统设了 125% 或 150% 缩放,个别版本的积木区会显示不全。遇到这种情况,先把系统缩放调回 100% 试一次,能确认是不是显示问题而不是软件坏了。
关于版本,我用过的一段时间里积木转代码的功能有过几次调整,所以下面讲到的菜单项和按钮位置,如果你手上的版本和我描述的位置对不上,按功能名字去找就行,别卡在"为什么我的界面上没有这个按钮"上。
2.2 界面上四个区域各管什么
打开软件后,界面基本分成四块,搞清楚谁是谁,后面就顺了:
代码编辑区是主战场,写 Python 代码的地方,有行号、有语法高亮。注意这里写的是真正的 Python 代码,不是某种简化方言,这一点很重要——你在这学的语法,换到任何其他 Python 环境都能用。
**舞台区(画布)**是海龟作画的地方,也是print输出的地方。有些版本会把图形舞台和文字输出分成两个面板,别看到print的结果没出现在画布上就以为程序没跑。
积木区放的是可拖拽的图形化指令,它和代码区是联动的:拖一个"前进 100 步"的积木过去,代码区会多出对应的t.forward(100)。
素材区/文件管理管的是图片素材、示例文件和保存的项目,做带图片背景的作品时会用到。
2.3 第一个程序:为什么第一行要 import
我建议的第一段代码永远是这个:
import turtle t = turtle.Turtle() t.forward(100) t.right(90) t.forward(100)跑起来你会看到画布上出现一个直角。这里有两行必须先解释清楚,不然学员会背代码而不知道在干什么。
import turtle的意思是"把海龟绘图这个工具箱搬进来"。Python 自带一大堆工具箱,默认不会全部加载,因为大部分程序用不上,全加载会又慢又占内存。turtle只是其中一个,你要用它就得先搬。
t = turtle.Turtle()是"造一只海龟"。这里的Turtle()是造物说明书,t是你给这只海龟起的名字。后面所有的t.forward、t.right,主语都是它。为什么要有这一步?因为一个画布上可以同时存在多只海龟,各画各的、互不干扰——这其实是后面讲"多对象"概念的伏笔,但第一节课不用展开。
顺便说一个新手高频问题:为什么海龟一开始朝右?因为 turtle 的角度体系里,0 度就是正右方向(东),角度逆时针为正。t.left(90)是抬头向上,t.right(90)是低头向下,t.setheading(180)是直接掉头朝左。这三条规则想清楚了,后面画任何图案都不会晕。
3. 积木和代码的对应关系:把turtle的核心API吃透
3.1 相对移动和绝对定位是两套思路
turtle 的移动指令分两类,混用是新手最容易乱的地方。
第一类是相对移动:t.forward(100)是"朝现在脸朝的方向前进100",t.backward(50)是后退,t.right(90)是"以现在朝向为基准顺时针转90度"。这类指令的坐标系是海龟自己的身体,它转到哪,前后的含义就跟着变。
第二类是绝对定位:t.goto(100, 50)是"不管你现在在哪、朝哪,直接跑到坐标 (100, 50)",t.setheading(0)是"不管你现在什么角度,直接摆到 0 度"。
为什么要分成两套?因为它们适合描述不同的东西。画正多边形、画螺旋、画分形树这类"每一步都基于上一步"的图形,用相对移动写出来特别短;而画坐标轴、画网格、在指定位置写文字这类"位置是固定的"任务,用绝对定位更直观,否则你得反推海龟当前在哪。
我课堂上有个小练习特别有效:让学员用goto画一个三角形,再用forward/right画同一个三角形,然后自己比较哪种写法改起来更方便。多数人的结论是:改尺寸的时候相对写法省事,改形状(比如把三角形挪到别的位置)的时候绝对写法省事。这个结论比老师讲十遍都管用。
3.2 画笔状态切换的顺序不能乱
turtle 里有个概念叫"抬笔/落笔",对应t.penup()和t.pendown()。抬笔状态下海龟移动不画线,落笔状态下移动就留痕。这个机制的存在意义是:画两个分开的图形时,中间的移动过程不应该留下痕迹。
这里有个顺序问题必须强调。看下面这段:
t.penup() t.goto(-200, 0) t.pendown() t.circle(50)抬笔、移动、落笔、画圆,顺序对了就是"跳到左边画一个圆"。如果反过来写成先落笔再 goto,那画布上就会多出一条从原点到目标点的斜线——这条线是新手最常见的"神秘斜线"来源,我见过太多人盯着它调了半天参数,其实就是抬笔忘了写。
填充的机制也要顺带讲清楚:
t.fillcolor("orange") t.begin_fill() for _ in range(4): t.forward(120) t.right(90) t.end_fill()begin_fill()和end_fill()之间的所有移动路径会被当作一个封闭区域填充。注意"路径"两个字——如果你在中间抬笔又落笔跳到别处,填充结果会跟你预期的不一样。另外填充的边界由路径决定,pencolor和fillcolor是两个独立属性,边框颜色和内部颜色可以不一样。
3.3 积木与Python写法对照表
下面这张表是我自己整理的常用对照,积木区里能找到的指令基本都在里面了。建议刚上手的人先把某一列抄一遍再运行,比单纯看更容易建立肌肉记忆。
| 功能 | 积木说法 | Python 写法 | 备注 |
|---|---|---|---|
| 前进 | 前进 N 步 | t.forward(100) | 也可写t.fd(100) |
| 右转 | 右转 N 度 | t.right(90) | 顺时针,相对当前朝向 |
| 移到坐标 | 移到 x,y | t.goto(0, 0) | 绝对定位 |
| 设置朝向 | 面向 N 度 | t.setheading(0) | 0度朝右,逆时针为正 |
| 抬笔 | 抬起画笔 | t.penup() | 简写t.pu() |
| 落笔 | 落下画笔 | t.pendown() | 简写t.pd() |
| 笔粗 | 设置画笔粗细 | t.pensize(3) | 也可写t.width(3) |
| 笔色 | 设置画笔颜色 | t.pencolor("red") | 支持颜色名或 RGB 元组 |
| 填充 | 开始/结束填充 | t.begin_fill()/t.end_fill() | 必须成对出现 |
| 画圆 | 画半径 N 的圆 | t.circle(50) | 第二参数是弧度范围 |
| 重复 | 重复 N 次 | for i in range(N): | 注意缩进 |
| 条件 | 如果……那么 | if 条件: | 注意冒号 |
| 写文字 | 写"内容" | t.write("你好", font=("Microsoft YaHei", 14, "normal")) | 字体是三元组 |
| 隐藏海龟 | 隐藏海龟 | t.hideturtle() | 出图干净 |
| 设置速度 | 设置速度 | t.speed(0) | 0 最快,1 最慢 |
| 清空画布 | 清空 | t.clear() | 海龟位置不变 |
表里有两个坑我提前标出来。一个是t.speed(),很多人以为数字越大越快,其实 0 是最快(直接跳),1 是最慢,中间 1 到 10 递减拖延时间。另一个是t.write()的字体参数,它是一个三元组(字体名, 字号, 字形),第三个参数不是字号而是粗细和斜体,写"normal"、"bold"或"italic",写成数字会报错。这个参数后面讲乘法表排版的时候还要细说。
4. 拆解热词:用海龟编辑器做一份"99乘法表"
4.1 先把"画在哪"这件事定下来
最近"99乘法表海龟编辑器"这个组合被搜得挺多,我猜大概率是课后作业或者练习题的场景。这类题目看着简单,其实有个前提必须先明确:乘法表到底输出到哪。
一种是在控制台输出,也就是用print打出九行文字,这考的是循环嵌套和字符串格式化;另一种是在画布上画出来,用海龟写文字、画网格线,这考的是坐标计算和排版。两者的知识点完全不同,难度也不一样。我下面两个都写,你可以按需要挑。
顺带说一句,这个题目本身设计得不错,因为它天然包含了三层结构:外层循环控制行、内层循环控制列、内层循环的次数还跟着外层变化(第 i 行只有 i 个式子)。这种"循环上限不固定"的嵌套,是初学者从"会写循环"到"理解循环"的分水岭。
4.2 控制台版本:三行核心逻辑与对齐的坑
先看代码:
for i in range(1, 10): row = "" for j in range(1, i + 1): row = row + f"{j}x{i}={j * i:<4}" print(row)拆开讲。外层for i in range(1, 10)管行,从 1 到 9;内层for j in range(1, i + 1)管列,注意上限是i + 1而不是固定的 10,这就是"第 i 行只有 i 个式子"的来源。range的右边界是取不到的,所以range(1, i+1)正好产生 1 到 i。
这里有个容易搞混的地方:为什么写的是{j}x{i}而不是{i}x{j}?因为乘法表的习惯写法是小数在前、大数在后,第 3 行写的是1x3、2x3、3x3。如果写成{i}x{j},出来的会是3x1、3x2、3x3,虽然数值都对,但和课本上的排版不一致,交作业容易被老师打回来。
对齐问题才是这个程序真正值得讲的部分。{j * i:<4}里的<4表示"左对齐,占 4 个字符宽度,不够的用空格补"。为什么要补?因为乘积的位数不一样,1x1=1是一位数,9x9=81是两位数,如果不补空格,每一行的长度就不一样,看起来参差不齐。
1x1=1 1x2=2 2x2=4 1x3=3 2x3=6 3x3=9我把宽度设成 4 是因为"j x i = 结果"最长也就是9x9=81这样的 6 个字符,加上空格刚好够一列。如果你觉得列太挤,把 4 改成 5 或 6 会更宽松。<表示左对齐(数字靠左、空格在右),>是右对齐,^是居中,这几个符号在格式化里通用,做表格输出的时候特别好用。
两个兼容性提醒。第一,f"..."这种写法的正式名字叫 f-string,Python 3.6 才开始支持,海龟编辑器内置的解释器版本一般都在这个之上,但如果你的版本比较老,把它换成"{}x{}={:<4}".format(j, i, i*j)更保险。第二,如果不想拼接字符串再一次性 print,也可以在内层循环里用print(..., end="")直接不换行输出,然后在外层循环末尾单独print()换行,两种写法效果一样,看个人习惯。
4.3 画布版本:用write和网格线把表格画出来
接下来是重头戏。要在画布上画乘法表,得先想清楚整体布局:准备画多大的格子、整个表格从哪个坐标开始、文字怎么在格子里居中。
我的做法是固定格子尺寸,然后反推起点坐标,这样表格永远在画布上居中:
import turtle t = turtle.Turtle() t.speed(0) t.hideturtle() turtle.tracer(0, 0) CELL_W = 80 CELL_H = 40 COLS = 9 ROWS = 9 ORIGIN_X = -COLS * CELL_W / 2 ORIGIN_Y = ROWS * CELL_H / 2 def draw_grid(): for i in range(ROWS + 1): y = ORIGIN_Y - i * CELL_H t.penup() t.goto(ORIGIN_X, y) t.pendown() t.goto(ORIGIN_X + COLS * CELL_W, y) for j in range(COLS + 1): x = ORIGIN_X + j * CELL_W t.penup() t.goto(x, ORIGIN_Y) t.pendown() t.goto(x, ORIGIN_Y - ROWS * CELL_H) def write_cell(row, col, text): x = ORIGIN_X + col * CELL_W + CELL_W / 2 y = ORIGIN_Y - row * CELL_H - CELL_H / 2 - 8 t.penup() t.goto(x, y) t.write(text, align="center", font=("Microsoft YaHei", 11, "normal")) draw_grid() for i in range(1, 10): for j in range(1, 10): if j <= i: write_cell(i - 1, j - 1, f"{j}x{i}={j * i}") turtle.update() turtle.done()几个关键设计我逐个解释。
为什么起点坐标要用负的单元格尺寸乘出来?画布的原点 (0, 0) 在正中间。想让 9 列宽 80 的表格居中,横向就得从-(9*80)/2 = -360开始,纵向从(9*40)/2 = 180开始往下排。这样不管你改格子尺寸还是行列数,表格永远居中,不用手调。
为什么要算文字的坐标而不是直接写格子坐标?t.write的坐标点是文字的锚点,配合align="center"就是水平居中的基准点,所以横坐标要加半个格宽,把点挪到格子正中。纵向麻烦一点:write的纵向基准在文字的基线附近,不是视觉中心,所以我在算完半格高之后又减了 8 个像素做微调。这个 8 不是理论值,是我实际调出来的经验值,字号变了它也要跟着变——字号 11 用 8,字号 14 大概要用 10 左右。
两行 tracer 相关代码是提速的。turtle.tracer(0, 0)的意思是关掉自动刷新,所有的绘制动作先在后台攒着;最后调一次turtle.update()把结果一次性刷到屏幕上。不加这两行,画 81 个格子加几十行文字,你会看到海龟在那里一笔一笔慢慢爬,等得心烦。加上之后基本是瞬间出图。
j <= i这个判断是画出下三角形。如果不加,你会得到一个完整的 9x9 方阵,但根据乘法交换律,右上角的内容和左下角是重复的,看起来冗余。加了判断只画下三角,视觉上更接近课本。
4.4 排版环节最常出问题的几个点
文字画出来之后,问题往往才刚开始。我把遇到过的排版问题整理成了一张表,方便对照排查。
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
| 文字跑到格子外面 | 格子宽度不够,或字号太大 | 加大 CELL_W 或减小字号 |
| 文字垂直方向贴顶 | 没做基线偏移 | 把 y 再减掉字号的 0.6 到 0.8 倍 |
| 中文显示成方块 | 字体名错误或系统缺字体 | 换成本机已有字体,Windows 试"Microsoft YaHei" |
| 表格整体偏出画布 | 行列数变了但起点没跟着变 | 检查起点坐标是否用了 COLS/ROWS 计算 |
| 运行后什么都没出现 | 用了 tracer 但忘了 update | 在最后补上turtle.update() |
| 网格线画得太慢 | 没关自动刷新 | 开头加turtle.tracer(0, 0) |
关于字体名,这里得说细一点。t.write的字体三元组里第一个元素是字体名,它必须在当前系统里真实存在,否则不同环境的行为不一样——有的会静默回退到默认字体(看起来还行,但字号字距会变),有的会直接抛异常。Windows 上比较稳的是"Microsoft YaHei"(微软雅黑),macOS 上可以用"PingFang SC"或者"Heiti SC"。写代码的时候如果你不确定目标机器是什么系统,可以先用一个不含中文的英文版本测试逻辑,跑通了再换中文字体——这样能把"排版问题"和"逻辑问题"分开排查,效率高很多。
还有一个进阶玩法值得试试:把下三角的每个格子按照乘积大小上色。用t.fillcolor()配合colormode(255)让颜色随乘积渐变,出来的效果比纯黑白表格好看得多。这一步的逻辑是把原来画网格的函数改成"先填充一个小方块再画边线",改动量不大,但对理解"颜色也是数据"这件事很有帮助。
5. 从重复到抽象:循环、函数和递归的推进节奏
5.1 什么时候该把代码改成循环
初学者常见的两个极端:一种是该用循环的时候硬写十遍,另一种是刚学循环就什么都往里套。判断标准其实很简单——重复的动作超过三次,并且每次只有某个数字在变。
画正方形的四种写法可以做教材:
# 写法一:全手写 t.forward(100) t.right(90) t.forward(100) t.right(90) t.forward(100) t.right(90) t.forward(100) t.right(90)八行。改成循环之后:
for _ in range(4): t.forward(100) t.right(90)四行,而且想画正六边形只要把 4 改成 6、90 改成 60。这里的 60 是怎么来的?正 n 边形的外角是 360 除以 n,因为海龟每画一条边就要转弯,转满一圈正好 360 度。这个几何原理必须讲,不然学生会以为 90 和 180 是 magic number。
下划线_是 Python 的惯例写法,表示"这个循环变量我用不到,但要占个位置"。写成for i in range(4)也完全正确,只是i从来没被用到,读代码的人会多问一句"这个 i 去哪了"。
再进一步,把边数变成变量,同一个函数就能画所有正多边形:
def polygon(t, n, size): for _ in range(n): t.forward(size) t.right(360 / n)polygon(t, 3, 100)画三角形,polygon(t, 8, 60)画八边形。函数第一参数传海龟对象,是为了让同一个函数能作用在多只海龟上,这个设计习惯在后面的项目里会一直用到。
5.2 参数化分三个层次推进
从"会写函数"到"会设计函数",中间隔着参数化思维。我在课上一般分三层推。
第一层是改数值。把固定的 100 换成参数 size,把颜色换成参数 color。这一层学员很快能掌握,本质是"把要改的东西挪到括号里"。
第二层是改形态。把边数、层数、分支角度这些决定形状本身的量参数化。比如画星星:
def star(t, size, points=5): angle = 180 - 180 / points for _ in range(points): t.forward(size) t.right(angle)五角星的angle是 144 度,它是怎么来的?五角星可以看成沿一个五边形走,但每次拐的是外角的补角,180 - 180/5 = 144。这个公式能直接推广到七角星、九角星,学生把 points 改成 7 看到图形跟着变,那个瞬间的表情是这门课最值回票价的部分。
第三层是改行为。比如加一个filled=True的参数决定要不要填充,或者加一个speed参数控制动画快慢。这一层开始涉及默认参数:
def star(t, size, points=5, filled=False, color="gold"): if filled: t.fillcolor(color) t.begin_fill() angle = 180 - 180 / points for _ in range(points): t.forward(size) t.right(angle) if filled: t.end_fill()默认参数的意义是"大部分时候用默认值,需要的时候才传"。star(t, 150)直接画一个不填充的金色五角星,想改风格再加参数。这就是真实项目里函数的常见形态,学员在这里第一次感受到"我写的东西别人能直接拿去用"。
5.3 递归:让图案自己长出来
递归是这套内容里最抽象的部分,但海龟绘图恰好是讲递归最好的载体,因为调用栈的展开过程可以直接画出来。
def tree(branch_len, t, depth): if branch_len < 5 or depth <= 0: return t.forward(branch_len) t.right(20) tree(branch_len * 0.7, t, depth - 1) t.left(40) tree(branch_len * 0.7, t, depth - 1) t.right(20) t.backward(branch_len)调用方式:tree(80, t, 6)。这段代码第一次看会晕,我一般这样拆:先讲出口。函数开头那个if就是递归的刹车——树枝短于 5 像素,或者层数用完,就停止,直接 return 回去。没有这个刹车,程序会一直递归到内存爆掉,编辑器直接卡死。
再讲镜像结构。右转 20 度画左边那根枝,然后左转 40 度(相对原来的位置就是右偏 20 度)画右边那根枝,最后右转 20 度把朝向还原。这三个角度加起来正好是零,所以函数返回的时候海龟的朝向和进来时完全一样,位置也因为backward回到了起点。这个"出去之前怎么改的,回来之前怎么改回去"的原则,是写递归图形最容易忽略、也最容易出 bug 的地方。
我通常会让学员做个实验:把最后那个t.right(20)删掉再运行。画面会立刻变成一边倒的歪树,因为朝向没有还原,每一层都在累积角度偏差。这个"删一行看后果"的操作比讲解十遍都有说服力。
至于为什么会看起来像真树,是因为branch_len * 0.7这个衰减系数——每往上一层,树枝长度变成前一级的 70%。自然界里很多分形结构都服从类似的缩放规律,你可以把 0.7 改成 0.6 或 0.8 看效果,0.6 长出来的树更瘦高,0.8 更矮胖。再进阶一点,把每次分支的角度加个随机扰动:
import random angle = random.randint(15, 30)出来的树每一棵都不一样,学生自己会玩很久。这时候再讲"随机数也是程序的一部分",接受度高得多。
6. 我在实际使用中踩过的坑
6.1 卡死和白屏的完整排查链路
这是最高频的问题,我按实际排查顺序说。
第一步,看画布是不是彻底没反应。如果窗口标题栏显示"无响应",八成是死循环。检查代码里有没有while循环,条件有没有出口。初学者最爱写的while True不加 break,程序会一直转,界面就卡住了。这种情况只能强制关闭,没别的办法,所以我在课上会反复强调:写 while 之前先想清楚什么时候停。
第二步,如果程序在跑但画布全白,想想是不是用了tracer(0, 0)却忘了turtle.update()。这个坑我自己也踩过,找了十几分钟才想起来。判断方法很简单:把 tracer 那行注释掉再跑一次,如果图形出来了,就是它。
第三步,如果画得特别慢但没卡死,大概率是元素太多、没关自动刷新。一次性画几百个格子、几千条线的时候,一定要开 tracer。
第四步,如果是递归相关的卡死,检查递归出口。出口条件里的临界值很关键,if depth <= 0和if depth == 0看起来差不多,但如果某一步 depth 从 1 直接跳到 -1(比如分支里传的是 depth - 2),用==就永远命中不了,直接无限递归。
| 症状 | 大概率根因 | 处理方式 |
|---|---|---|
| 窗口无响应 | while 无出口 / 递归无终止 | 强制关闭,补出口条件 |
| 程序结束但画布空白 | tracer 后未 update | 末尾补turtle.update() |
| 出图极慢 | 未关闭自动刷新 | 开头加turtle.tracer(0, 0) |
| 跑一半程序报错停止 | 参数类型不对或除零 | 看控制台报错的行号和类型 |
| 关掉窗口后程序还在跑 | 缺turtle.done() | 结尾补上 |
最后一行那个turtle.done()值得单独说。它的作用是让画布窗口保持打开、进入事件循环。不加的话,程序跑完主逻辑可能直接退出,窗口一闪就没了,你根本来不及看结果。有些版本会自动保持,有些不会,所以养成结尾加一句的习惯比较稳妥。
6.2 中文、编码和字体参数
涉及中文输出时有两个独立的坑,别混在一起。
一个是文件编码。如果代码里写了中文,文件保存时要用 UTF-8。保存成其他编码,在其他电脑上打开可能就是乱码,或者运行时直接报解码错误。海龟编辑器一般默认是 UTF-8,但如果你从别处复制代码进来,留意一下。
另一个是字体的选择。前面提过,t.write的字体三元组第一个参数必须是系统里真实存在的字体名。这里补充一个细节:font=("Microsoft YaHei", 12, "normal"),第二个参数是字号,第三个是字形。有人会把它写成font=("Microsoft YaHei", "bold")这种两个元素的写法,想当然以为省略字号就行,结果报错。三个元素一个都不能少,顺序也不能换。
还有个视觉上的小问题:同样的字号,在不同的字体下视觉大小差别挺大,微软雅黑 12 号看起来比 Arial 12 号小一点。所以如果你从英文示例里抄了字号,换成中文字体后可能需要调大一两号才顺眼。
6.3 素材、第三方库和文件保存
先说素材。素材区里的图片,有的版本是把文件复制到项目目录里,有的是引用原路径。如果属于后者,你换一台电脑打开同一个项目,图片可能就加载不出来了。判断方法:把整个项目文件夹整体拷走,到另一台机器上打开看看图片还在不在。不在的话,就把图片文件一起带上,放在项目目录里,代码里用相对路径引用。
再说第三方库的安装。海龟编辑器的好处是内置了安装第三方库的入口,但装不上的情况我遇到不少,原因基本是这三类:
- 网络问题导致下载超时。换成国内可访问的镜像源,具体地址在各个镜像站首页都能查到,复制到 pip 源配置里就行。
- 库的版本和内置 Python 版本不匹配。有些库只支持比较新的 Python,装的时候会提示需要更高版本,这种就换一个功能相近的老版本,或者放弃。
- 装完之后没重启编辑器,在代码里 import 依然报"找不到模块"。这个不是安装失败,重启一下就好。
顺便提醒,不要在这个环境里装太重的库,它毕竟定位是教学工具,跑不动大型的计算框架。想做数据分析、机器学习的话,早点切到专业的编辑器里去。
6.4 积木和代码混用时最隐蔽的一个问题
这个坑我觉得比前面几个都隐蔽,因为它不报错,只是让你白写代码。
在积木模式下拖拽操作时,切换到代码视图,代码区的内容是根据积木重新生成的。也就是说,如果你先用积木搭了个图案,又切到代码区手写了一段循环,然后切回积木模式——再切回来的时候,你手写的那段很可能就没了,因为代码被积木的当前状态覆盖了。
我给出的规避方案有两条。第一条是养成另存为独立文件的习惯,一旦开始手写代码,就在文件管理里新建一个单独的.py文件,完全离开积木模式来写。第二条是认清阶段:积木模式适合建立"程序是有顺序的、循环是重复"这种直觉,代码模式才是真正学编程的地方,两到三次课之后就应该主要用代码模式,积木只偶尔回头对照一下。
另外,代码文件后缀是.py,这是标准 Python 文件。这一点挺重要——意味着你在这里写的作品,换到装好了 Python 的电脑上,用python 文件名.py就能直接运行。我有时候会把学生作品拷到另一台机器上跑一遍给他们看,让"我学的是真东西"这件事变得具体。
7. 同一份工具怎么喂饱不同水平的人
7.1 三档任务设计
带班的时候最头疼的是水平参差,同一节课有人十分钟做完,有人四十分钟还在找按钮。我一般准备三档任务,让快的往下走,慢的先把第一档吃透。
| 档次 | 任务内容 | 训练的知识点 | 完成标准 |
|---|---|---|---|
| 基础档 | 照着代码改参数,画出彩虹色螺旋 | 顺序执行、基本指令 | 能说出改的那个数字控制什么 |
| 进阶档 | 用循环和函数画一组正多边形拼成的图案 | 循环、函数、参数、角度计算 | 改一个参数,整个图案跟着变 |
| 挑战档 | 独立完成一个"99乘法表"或自选小作品 | 嵌套循环、坐标计算、排版、随机 | 能给别人讲清楚自己的思路 |
彩虹螺旋的代码短得出奇,效果却很抓人:
import turtle t = turtle.Turtle() t.speed(0) turtle.colormode(255) for i in range(120): t.pencolor(i * 2, 100, 255 - i * 2) t.forward(i) t.right(59)colormode(255)是把颜色范围切换到 0-255,切换之后pencolor(r, g, b)才能用三个数字表示颜色。不切换的话默认范围是 0-1,得写成小数,新手写起来别扭。i * 2让红色分量随循环增长,255 - i * 2让蓝色分量递减,颜色就从蓝渐变到红。right(59)这个角度故意选了个除不尽 360 的数,所以螺旋不会闭合,才会出现层层错开的漂亮纹理——如果写成 60 度,六次之后会回到原位,图案就变成封闭的六边形了。这个细节我每次都要讲,因为它把"角度选择"和"视觉结果"直接关联起来了。
7.2 讲评的时候问什么问题
判断一个人是"抄会了"还是"真会了",我有一个简单的提问法:如果我改掉这个数字,图形会变成什么样?
能答出来的人,说明他脑子里有模型;答不出来但能试出来的人,说明他会用工具验证;既答不出来又不知道怎么试的人,说明还在记忆阶段。这三种状态我都见过,也都能往前推,方法完全不同——第一种可以直接进挑战档,第二种要逼着他先说出预期再运行,第三种得回到最基础的一行代码一行代码地读。
给反馈的时候我尽量不说"你写得不对",而是问"你想要的是哪种效果,现在出来的是哪种,差在哪一步"。这个问法逼着人把"预期"和"结果"分开,是调试能力的起点。程序的报错信息也是同样的用法——不要急着告诉答案,让他在报错的最后一行找英文单词,对照代码的行号,自己定位。
7.3 什么信号说明该换工具了
我在第 1 节提过换工具的时机,这里再补一个更具体的判断标准:当你开始觉得这个环境的限制比它的帮助更大时。
具体表现是:你想读写电脑上的文件,发现操作起来绕;你想把代码拆成好几个文件互相调用,发现管理起来别扭;你想装一个稍微大一点的库,发现跑不动或者根本装不上;你想让别人不装这个软件也能用你的程序,发现做不到。这些情况出现的时候,就该收拾东西准备搬了。
反过来,如果你还在"这个图案怎么画、那个循环怎么写"的阶段,就安心待着。工具的价值是有期限的,用对阶段比用对工具更重要。
最后分享一个小技巧,是我在自己折腾时偶然发现的:把海龟绘图的速度设成t.speed(5)左右,然后录屏,用正常速度播放,出来的效果就像手绘过程的动画。给家长看学习成果、或者给同学做展示的时候,比起直接甩一张成品图,这种"过程感"的说服力要高得多。而且录制过程本身也是一种检查——你会发现有些你以为写得很顺的地方,实际运行时海龟绕了一大圈才画到那里,那就说明代码结构还有优化的空间。