Python turtle库入门:从画小房子到递归分形树的编程启蒙

发布时间:2026/9/9 5:27:07
Python turtle库入门:从画小房子到递归分形树的编程启蒙 1. 小海龟不止是儿童玩具为什么我强烈推荐用turtle开启Python绘画之旅如果你刚接触Python或者已经学过几节课但总觉得没意思那我建议你试试换个思路别急着去啃那些枯燥的语法先让代码画点看得见摸得着的东西出来。我自己在带零基础朋友入门的时候发现一个通病——第一课让他们print(Hello World)第二课可能还有兴趣到第三课讲变量和字符串拼接很多人就开始打退堂鼓了。原因很简单反馈太抽象。你在屏幕上敲出一行文字屏幕上再冒出一行一模一样的文字这种成就感顶多维持三分钟。但如果你输入几行代码屏幕上真的慢慢画出了一栋带烟囱的小房子、一棵有枝丫的大树那种“我真的在创造东西”的感觉是完全不一样的。这也是为什么我一直觉得Python自带的turtle库被严重低估了。很多人把它当成小学生学编程的玩具但实际上turtle库对成年人学习编程来说同样是一个极佳的工具。它不需要你事先理解复杂的图形学知识也不用安装任何第三方依赖只需要Python标准库就能运行。更妙的是它会强制你建立一种“程序员的思维方式”把一幅画拆解成无数的直线、曲线、角度和坐标然后一步步用指令去执行。这个拆解和组合的过程恰恰就是编程的核心。等你用turtle画完几幅作品再回头去看循环、函数、变量这些概念会有一种豁然开朗的感觉——原来它们不是孤立的语法点而是你刚才使用的每一个动作的抽象总结。这篇文章我们就从零开始一步步用代码画出你的第一幅艺术作品。我会先带你安装并验证环境然后拆解turtle的核心绘图逻辑接着写一栋带天空、草地、太阳和树木的彩色小房子再进阶到用递归画一棵随机生长的分形树。整个过程不需要任何美术功底你只需要能看懂坐标、角度和简单的Python语法就够了。1.1 安装Python最容易忽略的一步环境变量的坑很多教程会默认你已经装好了Python但实际教新手的过程中我发现“明明装了Python却用不了”是最常见的起步障碍。问题大多出在安装时漏掉了一个关键选项Add Python to PATH。如果你去Python官网下载了安装包双击运行后会在第一步看到一个很大的复选框列表位于窗口最底部。你需要在“Add Python to PATH”前面打勾再点Install Now。这个操作的意思是让系统在任何目录下都能认出python和pip这两个命令。如果不勾选Python确实也装进去了但你打开命令行输入python时系统会一脸茫然地告诉你“不是内部或外部命令”或者更离谱的直接把Windows商店里那个假Python的下载页面弹出来。已经装完但没勾选PATH也别急不需要重装。打开系统设置搜索“环境变量”找到Path在列表里加上两条路径一条是Python的实际安装路径比如C:\Users\你的用户名\AppData\Local\Programs\Python\Python312\另一条是它下面的Scripts子目录。添加完记得关掉命令行窗口再重新打开设置才会生效。安装完成后在命令行输入python --version能看到版本号比如Python 3.12.1就说明可以开始用了。turtle库是Python标准库的一部分跟print、math一样不需要额外pip install只要你装的是官方版本它天然就在那里。1.2 验证阶段屏幕上的第一只小海龟环境准备好后我建议先别急着写大段代码而是用一个最小的验证脚本找到“手感”。打开你常用的编辑器新建一个文件比如叫hello_turtle.py输入下面三行import turtle t turtle.Turtle() turtle.done()第一行导入turtle模块第二行创建了一只小海龟对象命名叫t第三行turtle.done()的作用是让窗口保持打开状态。很多新手在第一步就会踩坑运行这个脚本后画布窗口闪了一下就消失了根本看不清里面有什么。原因就是你忘了加第三行。当Python执行完所有代码后程序会正常结束turtle创建的窗口也随之关闭。加上了turtle.done()程序会一直监听画布上的事件直到你手动关闭窗口为止。我还见过另一种情况代码明明写了done()窗口依然不显示或者隔了很久才弹出来。这多半是编辑器的问题比如在VS Code的集成终端里运行tturtle偶尔会因为终端缓冲问题导致窗口刷新不及时。这时候可以把终端切到“外部终端”运行或者直接在系统命令行里执行python hello_turtle.py基本都能解决。顺利的话你会看到一个白色的画布窗口中间有一只黑色的小箭头模样的图标。别再觉得它只是图标了——它就是你即将指挥的画笔你写的每一行绘图指令都会以它为起点。2. 先别急着画房子海龟画图的底层逻辑是坐标加方向用turtle画图本质上是在回答三个问题从哪个点开始朝向哪个方向走多远这跟在纸上画画完全不同因为你不能一上来就用曲线勾轮廓。你得像指挥一个严格执行命令的机器人一样告诉它前进多少像素、左转多少度、抬笔还是落笔。整个过程非常机械但也正是这种机械性反过来逼迫你把一个模糊的视觉意象拆解成精确的逻辑序列。如果你直接从网上复制一个画枫叶或小人的教程即使代码跑通了你大概率也是一头雾水下次换个图形照样不会画。所以先花点时间把海龟的运动规则内化后面所有项目都会顺畅很多。2.1 默认坐标系和初始方向小海龟的“出生配置”打开turtle默认的画布中心点就是坐标系的原点(0, 0)向右是x轴正方向向上是y轴正方向。这个跟你在数学课上学到的直角坐标系完全一致。但有一个细节很容易被忽略小海龟的初始朝向是向右也就是x轴正方向。我见过不少新手画正方形代码长这样t.forward(100) t.right(90) t.forward(100) t.right(90) t.forward(100) t.right(90) t.forward(100)这段代码没问题跑起来也确实能画出一个正方形。但你有没有想过如果初始朝向不是向右这段代码画出来的图形会变成什么样方向不同图形会整体旋转但边长和角度关系不变。理解了这一点你对right()和left()的理解就不再是“向右转”和“向左转”而是“顺时针旋转”和“逆时针旋转”。这个区别极其重要因为右转和左转是相对于海龟当前朝向而言的跟屏幕的固定方向没有直接关系。默认朝向为右所以第一次t.right(90)会让海龟朝屏幕下方而不是你直觉里的屏幕右侧。多转几次后海龟会依次朝向下、左、上最终回到初始朝向。2.2 核心指令速查先掌握这10个就够了turtle库提供了大量方法但作为新手阶段只需要熟练掌握下面这张表就完全够用。等你用得熟练了再去查其他API也不迟。指令作用一句话说明forward(d) 或 fd(d)沿当前方向前进d像素核心移动指令d越大走得越远backward(d) 或 bk(d)沿当前方向后退d像素与forward方向相反但朝向不变right(angle) 或 rt(angle)当前朝向上顺时针旋转angle度不移动只转头left(angle) 或 lt(angle)当前朝向上逆时针旋转angle度不移动只转头penup() 或 up()抬笔抬笔后移动不会在画布上留下痕迹pendown() 或 down()落笔默认状态移动时画线goto(x, y)移动到画布上绝对坐标(x, y)位置配合抬笔使用可实现“瞬移”circle(r)以当前位置为圆心画半径为r的圆圆的起点和方向受当前朝向影响默认从当前点逆时针画begin_fill() / end_fill()在两者之间的图形中填充当前填充色先要设置fillcolorcolor() / pencolor() / fillcolor()设置颜色color可以一次设置笔色和填充色pencolor只设置线条颜色pensize(n)设置线条粗细数值越大线条越粗speed(n)设置绘图速度speed(0)最快speed(1)最慢speed(6)适中hideturtle()隐藏小海龟图标画完作品后隐藏图标画面更干净从这张表你也可以看出画图过程中最常用的动作组合是抬笔、goto到指定位置、落笔、设置颜色、移动或旋转、开始填充、移动形成闭合图形、结束填充。如果你把这个组合练熟了基本上已经能画任何多边形和组合图形了。2.3 循环与几何结合画一个正方形能学到什么我见过很多人急于求成不想在简单图形上花时间结果到了复杂图形上反复出错。画正方形这件事表面上是四条边加四个直角但对新手来说它是理解循环结构最好的载体。import turtle t turtle.Turtle() t.speed(1) for _ in range(4): t.forward(100) t.right(90) turtle.done()这段代码用for循环把前进和右转各执行了四次效果跟手写四遍一模一样。你如果运行一下会看到小海龟先向右走100像素然后顺时针转90度再走100像素再转90度如此循环四次最后恰好回到起点指向初始方向。这里的规律是任何闭合的多边形外角总和永远是360度。正方形四个外角都是90度三角形三个外角都是120度五角星的五个拐角是144度。所以你不用记每个图形的外部角度只需要记住外角和360度这条不变量用360除以图形的边数或角数就能得到每次要旋转的角度。把正方形练习吃透后再试试这个变形把range(4)改成range(30)把right(90)改成right(12)再用speed(0)你会看到一个类似扇形的图形。或者用range(360)加right(1)它就会越来越接近一个圆。这种从正方形到圆形的连续变化过程比直接告诉你“circle(50)可以画圆”要直观得多。你会在视觉上理解为什么圆可以被看作一个正三百六十边形为什么计算机图形学里没有真正完美的圆只有足够多边形的近似。2.4 填充与抬笔让画面不再是简笔画画正方形只能得到轮廓线但如果想给小房子的墙填上颜色就得用到begin_fill和end_fill这一对方法了。它们的关系有点像给图形区块上色的开关先调用begin_fill告诉Python“我要开始填充了”接着画一个闭合图形最后调用end_fill通知Python“图形画完了现在把填好的颜色粉刷上去”。在begin_fill之前的fillcolor设置会自动起作用。比如果实import turtle t turtle.Turtle() t.speed(5) t.fillcolor(lightpink) t.begin_fill() for _ in range(4): t.forward(80) t.left(90) t.end_fill() # 在另一个位置画第二个方块 t.penup() t.goto(150, 0) t.pendown() t.fillcolor(lightblue) t.begin_fill() t.circle(40) t.end_fill() turtle.done()这段代码里还出现了penup和goto的配合使用。为什么要抬笔因为如果你不抬笔海龟移动时会拖出一条连接两个图形的直线这个线条通常不是你想要的。抬笔过后移动相当于在纸上悬空移动笔尖到了新位置再落笔。这个组合是后面所有项目里最常用的基础动作没有之一。养成“先抬笔、再goto、最后落笔”的肌肉记忆你的画布上就不会出现奇怪的连线了。3. 第一幅完整作品拆解用代码画出带天空和草地的彩色小房子接下来进入正题画一幅像样的作品。为了让视觉效果好一点我没有选择只画一个孤零零的房子而是把场景做完整画面左侧是草地和一棵小树居中是一栋粉墙红顶的房子屋顶上方有浅蓝色天空和带光芒的太阳。整个画面大约800像素宽、600像素高全部使用turtle的标准绘图能力完成。你先把完整代码跑通再看后面的逐步拆解。3.1 整体代码天空、草地的绘制思路运行的画布默认大小可能不够舒服所以先用turtle.setup(800, 600)把画布设成宽800高600。然后先画天空和草地两个色块。没有它们房子就飘在空中作品会失去空间感。草地区域我定位在画布下半段用一个覆盖左下角到右下角的矩形完成再用一棵简单的小树点缀。天空部分用浅蓝色填充画布上半部。实现方式其实很笨但有效把海龟抬笔走到画布一角落笔后通过循环画一个长方形再填充颜色。import turtle t turtle.Turtle() t.speed(6) t.pensize(2) turtle.setup(800, 600) # 天空 t.penup() t.goto(-400, 100) t.pendown() t.color(lightblue) t.begin_fill() for _ in range(2): t.forward(800) t.left(90) t.forward(300) t.left(90) t.end_fill() # 草地 t.penup() t.goto(-400, -200) t.pendown() t.color(lightgreen) t.begin_fill() for _ in range(2): t.forward(800) t.left(90) t.forward(300) t.left(90) t.end_fill() turtle.done()这段代码跑起来后你看到的画面是水平上下分割的两个色块上方浅蓝下方浅绿分界线在y100这个位置。为什么分界线不平移到y0呢因为画布中心是(0,0)把地平线稍微抬高一点山丘或房子的地基就能落在视野的下部画面构图更接近人的日常观察习惯。这个看似随意的数值其实是构图经验。用循环画矩形时for _ in range(2)只循环两次每次做“前进800像素、左转90度、前进300像素、左转90度”刚好画完一个矩形的四条边并回到原点。这是一个非常高效的多边形画法四个边的矩形只需要两行循环体。3.2 房子主体的填充画法地平线准备好后开始画主角——小房子。为了让房子视觉重心不要太飘我把它的左下角定位在(-160, -100)附近。墙体是一个边长200的正方形填充色我选择了带一点暖色调的浅粉色这样配合红屋顶会更建可爱。先抬笔定位到墙体的左下角落笔填充前先把t.fillcolor(pink)设置好然后画正方形。# 房子墙壁 t.penup() t.goto(-160, -100) t.pendown() t.fillcolor(mistyrose) t.begin_fill() for _ in range(4): t.forward(200) t.left(90) t.end_fill()画正方形时用left(90)和right(90)效果一样因为四个角都是90度无论顺时针还是逆时针最终都回到起点。这里我刻意选用left(90)是因为图形是站在地平线上绘制的海龟先向东走然后向北、西、南逆时针转圈跟生活中按逆时针方向画外轮廓的习惯一致。3.3 屋顶的等腰三角形怎么画屋顶是这幅画里最需要动脑筋的地方。墙壁上方的宽度是200像素屋顶需要比墙体略宽一些所以我让屋顶的底边从x-180延伸到x180总宽360。屋顶的高取120像素那么屋顶尖的坐标就是(0, 20)。由于屋顶的底边与墙体上边在视觉上差了一段距离这里需要先让海龟移动到屋顶左端点位置。用goto直接跳过来画三角形会非常方便。# 三角形屋顶 t.penup() t.goto(-190, 100) t.pendown() t.fillcolor(firebrick) t.begin_fill() t.goto(0, 240) t.goto(190, 100) t.goto(-190, 100) t.end_fill()用goto而不是forward转left可以让三角形的位置计算固定在绝对坐标上。屋顶左端点坐标我设置为(-190, 100)屋顶尖为(0, 240)右侧为(190, 100)这样三个点连线后就是一个等腰三角形。高度差是140像素比墙壁上方的位置高出很多正好能覆盖住墙体顶部。这段代码里goto接连用了三次海龟先从屋顶左端点连线到屋顶尖再连线到右端点最后闭合回左端点。goto一个点是“移动到这个点并画线”所以不需要额外画闭合线。我调色时墙体选浅色屋顶选深红色形成对比。画画时颜色搭配直接影响成品效果这个经验在turtle里同样适用。对于新手来说可以先直接用英文颜色名等以后再用十六进制色值进行更细腻的调色。3.4 门窗和太阳用绝对定位把画面补满房子主体完成后再给房子加上门和窗户。这个步骤涉及的所有元素都用同一种套路抬笔、goto到一个确定性坐标、落笔、填充、画形状。窗口定位在墙体左半部分门定位在底部中间偏右。为了让画面有层次感我用saddlebrown画了门用cyan画窗体在窗棂上再画两条交叉线。# 门 t.penup() t.goto(-30, -100) t.pendown() t.fillcolor(saddlebrown) t.begin_fill() for _ in range(2): t.forward(60) t.left(90) t.forward(100) t.left(90) t.end_fill() # 窗户 t.penup() t.goto(-130, 0) t.pendown() t.fillcolor(lightcyan) t.begin_fill() for _ in range(4): t.forward(50) t.left(90) t.end_fill() # 把窗户画出十字窗格 t.penup() t.goto(-130, 25) t.pendown() t.forward(50) t.penup() t.goto(-105, 0) t.pendown() t.left(90) t.forward(50)这段代码里有个细节值得注意画完窗户的边框后海龟的位置会停在窗框的左上角也就是下一次forward的方向朝向左侧。为了画横窗格我先抬笔跑到窗户的左中点然后方向未知的情况下直接forward(50)大概率会画歪。所以最好用goto定位起点并把方向重新摆正。我习惯于在画横线前先t.setheading(0)让海龟恢复向右画竖线前再用t.setheading(90)让海龟朝上。加了这个动作后就不必担心之前旋转动作残留的方向影响了。接下来在画面右侧画一个太阳。太阳主体就是黄色圆然后用8条直线光芒表示放射方向。# 太阳 t.penup() t.goto(180, 240) t.pendown() t.fillcolor(gold) t.begin_fill() t.circle(40) t.end_fill() # 太阳的光芒 t.penup() t.goto(180, 280) t.pendown() for _ in range(8): t.forward(30) t.backward(30) t.right(45)为什么太阳中心定位在(180, 240)的时候circle(40)画出来的圆心不在(180, 240)因为turtle的circle方法并不是以当前点作为圆心的而是以当前位置作为圆的底部或者取决于朝向位置它默认从当前点开始沿逆时针画一个圆当前位置是圆的起始点。但实际中你写t.circle(40)它会从当前点开始向左后方画圆也就是说圆心在起始点的正上方。所以当你把海龟定位在(180, 240)时圆心其实在(180, 280)位置这跟想要的位置差了一个半径的距离。我还是希望圆心中心在目标位置所以先抬笔把海龟移到(180, 280)再circle(40)。这个经验对后续画同心圆和复杂场景很有用。我一直强调画精确图形之前先在纸上或脑子里把坐标图转一转把“海龟当前位置会落在图形的哪个位置”搞清楚再动手写circle命令这样可以省去来回调试的时间。3.5 添加细节小树、烟囱和点缀后作品感就出来了一栋房子如果只有墙、屋顶、门、窗、太阳还是显得有点单薄。为了让画面更像一幅艺术创作我又加了小树和烟囱。树可以画成棕色树干加三个绿色同心圆树冠烟囱则在屋顶上画一个矩形小方块并在烟囱口画几圈袅袅上升的线。# 树 t.penup() t.goto(-280, -120) t.pendown() t.fillcolor(sienna) t.begin_fill() for _ in range(2): t.forward(30) t.left(90) t.forward(80) t.left(90) t.end_fill() # 树冠 t.penup() t.goto(-265, -40) t.pendown() t.fillcolor(forestgreen) t.begin_fill() t.circle(35) t.end_fill() t.penup() t.goto(-295, -20) t.pendown() t.begin_fill() t.circle(25) t.end_fill() t.penup() t.goto(-235, -20) t.pendown() t.begin_fill() t.circle(25) t.end_fill()这段代码产生的效果是棕色矩形向上立起顶端三个绿色圆形像三团云朵叠在一起。树的底部坐标关系到草地的位置所以放在y-120附近这样树干不会悬在半空。烟囱的位置要注意和屋顶配合。红色三角形房顶的轮廓是固定的烟囱坐标应放在屋顶斜面内不能超过屋顶尖同时视觉上必须比屋顶高一点才能让人看出是烟囱。在代码里烟囱底部定位在(80, 130)宽度45高度70。# 烟囱 t.penup() t.goto(80, 130) t.pendown() t.fillcolor(gray) t.begin_fill() for _ in range(2): t.forward(45) t.left(90) t.forward(70) t.left(90) t.end_fill() # 烟雾几段小圆 t.penup() t.goto(120, 240) t.pendown() t.pencolor(lightgray) t.circle(10, 180) t.penup() t.goto(138, 270) t.pendown() t.circle(15, 180) t.penup() t.goto(162, 305) t.pendown() t.circle(20, 180)circle的第二个参数180表示只画半圆非常适合画这种袅袅上升的烟雾线条。如果不加第二个参数它会画一整圈那就不是烟雾而是圆圈了。微调定位之后烟雾曲线从烟囱口向右上方飘散画面一下子就有了生活气息。3.6 从这段项目代码里沉淀下来的三点经验第一整个作品的核心把控点其实不在画画本身而在坐标计算。我强烈建议你在写代码前先在白纸上画一个800乘600的网格图标出房子的左右边界、草地的分界、树的坐标。没有这张草图你就会陷入不断用试错法调坐标的循环里。我一开始直接凭感觉定位结果发现屋顶和墙体错位了20像素门窗的位置也歪了后来多花了很多时间。画草图虽然看起来多此一举实际能省下好几倍的调试时间。第二颜色填充之后要马上涂掉不需要的边界就用begin_fill和end_fill包住要上色的图形不需要考虑重叠图形的边界线。由于end_fill会自动从最后一点连到第一个点并填充所以矩形、三角形等闭合图形非常容易设置填充色。第三在绘制顺序上先画背景再画主体先画大面积再画细节。如果你先画窗户再画墙墙面会盖住窗顺序搞错了要反复重画。这个和画油画时的图层逻辑一模一样。4. 进阶玩法用递归画一棵随机分形树体验“生成艺术”小房子画完以后你的turtle基础已经足够扎实了。但如果每次画树都要我手动画树干、一个个圆树冠那么工作量极大且每棵树看起来都不太自然。这时候就轮到计算机最擅长的领域登场了——用递归生成一棵有随机生长感的分形树。这个项目不需要你学任何新库只需要掌握一个可以让代码看起来非常“聪明”的编程技巧递归函数。4.1 递归是什么它跟画树有什么关系递归这个概念听起来吓人其实说白了就是函数自己调用自己。它跟循环很相似都是重复执行某段逻辑。区别在于递归往往会把一个问题拆成更小的同类问题而循环更偏于固定次数的重复。拿画树来说树的生长规律可以用一句话概括树干在末端分叉出两个较短的枝干每个枝干继续生长又在末端分叉出两个更短的枝干如此反复直到枝干短到某个体积以下就不再继续。这个定义本身就是递归的画树函数在执行时先画一段粗树枝然后调用“画树函数”来画较细的左枝再调用一次来画较细的右枝直到长度小于设定的阈值。为了制造自然感每次递归我都给分叉角度和长度加入随机数。这样即使规则一样每次运行产生的树也会因随机数而变化形成大自然里独一无二的艺术作品。4.2 分形树的完整代码演示下面这段代码可以直接运行。画出来的树会从画布底部向上生长枝干从棕色逐渐过渡到绿色。每一轮递归都会产生两个略微左右摇摆的新枝看起来特别接近真实植物的生长逻辑。import turtle import random t turtle.Turtle() t.speed(0) t.pensize(20) t.pencolor(saddlebrown) t.penup() t.goto(0, -300) t.setheading(90) t.pendown() def draw_tree(branch_len, t): if branch_len 15: t.pencolor(green) t.pensize(3) t.forward(branch_len) t.backward(branch_len) return t.pensize(branch_len * 0.15) t.forward(branch_len) angle1 random.randint(20, 35) angle2 random.randint(20, 35) shrink random.uniform(0.7, 0.75) t.right(angle1) draw_tree(branch_len * shrink, t) t.left(angle1 angle2) draw_tree(branch_len * shrink, t) t.right(angle2) t.backward(branch_len) turtle.tracer(0) draw_tree(120, t) turtle.update() t.hideturtle() turtle.done()把这段代码跑起来你会看到整棵树从底部到树梢线条越来越细、颜色从棕色渐变成绿色。turtle.tracer(0)和turtle.update()的组合会让整棵树瞬间长出来而不是一帧一帧被看完避免等待时间过长。4.3 为什么分形树必须画完分支后退回原地这段代码里的关键动作是最后一步t.backward(branch_len)。很多人第一次自己写递归画树逻辑时会忘记把画笔退回到分叉点。你可以做一个实验删除最后一行backward再运行一次你会看到树枝像被吹乱了一样各层级交织在一起根本无法形成树冠。原因是这样的递归调用draw_tree内部会消耗掉一定的前进和后退。假设某个树枝长度为100海龟先前进100到达这个树枝的末端之后递归画它的左右分支。左右分支的递归函数内部无论怎么forward、right、left函数结束的时候海龟已经通过各自的backward退回到了这个树枝的末端。也就是说当两个递归调用都完成时海龟还停留在当前树枝的末端而不是树干原来的位置。如果函数直接返回调用它的上一级函数根本不知道应该从哪里继续。那个backward的作用就是让海龟沿着来时的路退回到起点还原整个画布上的位置与状态。这个做法在递归绘图里被称为“状态恢复”。不仅画树如此以后你画迷宫或分形图形时都会需要这种做法。理解了这一步你就理解了一个通用递归模板的内在逻辑先往前走然后递归子问题最后回到原来的位置。这个套路可以画出很多漂亮的递归图案像是分形蕨类、雪花曲线、科赫雪花都是由这个模板变形而来的。4.4 调参与选择随机范围每棵树都有一点点个性为了让树不至于太对称我设置了两个随机角度angle1和angle2分别代表右转和左转的幅度范围在20到35度之间。每层分支缩小的比例也被定义成shrink范围在0.7到0.75之间。这样树的下层枝干会比上层粗不少视觉上更自然。如果你想让树显得修长可以加大左右角度的差值比如一边20一边40这样偏斜会很明显如果想画一棵“矮胖”的树可以把shrink调高到0.85让枝干衰减得慢一些树冠会显得浓密。颜色的切换点由branch_len 15这个阈值决定。把阈值提高到30那么树叶会变多看起来更茂盛降低到5树叶就只能在很小的枝干末梢出现树看起来会比较稀疏。我自己调了很多次参数之后的感觉是随机数范围越小画的树越像同一种模板长出的不同个体随机数范围越大树的形态差别就越大甚至可能出现明显的一边倒。想要稳定的“森林感”shrink的稳定性比角度更重要。shrink相当于控制整棵树生长的资源总量。4.5 随机分形树与前面小房子的区别艺术感和技术感结合的甜点在小房子项目中你是那个在纸上规划好一切的设计师代码只是在执行一套确定性的指令。而在分形树项目中你更接近于定义一套“规则”然后让代码在规则里自己探索出形态。这也是我特别推荐在学编程的初期就接触分形树的原因——它不会像练习题那样刻板每一次运行都给你一个不同的结果比单纯画静态图好玩太多了。你可以在树旁边加一个太阳也可以同时生成五六棵树摆成一排背景用淡蓝色地面用绿色立刻就是一幅具有装饰感的数字艺术作品。而且你会发现学习函数和递归之前那些很抽象的概念在这里实实在在地变成了“你掌握的一种画笔”。5. 运行过程会被这几个问题拦住窗口闪退、笔没抬起来、方向弄反了不管画小房子还是分形树新手运行turtle脚本时总会被几个通用问题狠狠折磨。我干脆把它们集中写在一个章节里免得你每跑一个脚本都来问一次。5.1 窗口一闪而过不是代码写错了是少了“看门狗”最最常见的现象是运行代码后画布出现不到一秒就自动关闭了你根本来不及看清画出来的东西。这个问题在之前提过根源在于程序执行完毕时窗口随之关闭。解决办法就是在脚本最后一行的绘图指令后面加一行turtle.done()或turtle.exitonclick()。turtle.done()让窗口保持打开直到你手动关闭turtle.exitonclick()则需要你点击画面才会退出很适合逐帧观察画图过程。我给初学者的建议是直接写exitonclick()一旦效果满意再改成done()也行。需要注意的是done()之后不能再接任何绘图指令。如果你想在点击后再让海龟画点什么需要用onclick绑定事件。对于目前的项目把这个函数理解为脚本的“句号”就好。5.2 图画出来了但位置飘在半空或跑出界坐标系没搞明白另一种常见情况是图形的主体画对了但位置不对跑到了画布边缘或者偏离了预期区域。原因基本是坐标定位的混乱。turtle的坐标原点默认在窗口中心和很多图像处理库的左上角原点不一样。比如你在网上看到某个人分享的代码里用的坐标是(100, 100)那通常是基于他们自己设置的画布大小得到的。如果你的setup跟他的不同图形位置自然也会不同。在动手之前先看清你的画布中心到底在哪里。你可以用t.setpos(0, 0)或t.home()让海龟回到中心点再画一些简单的参考线确认边界。还有一种笨办法就是运行代码后打开画布用turtle做过墙的坐标打印把当前海龟位置的x和y用t.pos()查看。这部分代码调试速度