
还记得我第一次在朋友面前演示用Python画图就用了一个大家可能觉得“很幼稚”的库——turtle。当时刚从命令行切到图形窗口一只黄不拉几、线条歪歪扭扭的皮卡丘被一行行代码慢慢“长”出来的时候那种成就感确实不是打印几行 Hello World 能比的。今天要分享的这个小项目就是这么回事用Python自带的turtle库从零开始画一只皮卡丘。内容适合Python刚入门、想玩点“非控制台”东西的人也适合老手想快速给学员或同事做个趣味演示。画得像不像不是重点重点是你把一张卡通角色拆成几何形状、再把形状变成坐标和路径的那套思路学会了它turtle能画的东西比你想象中多得多。整个项目不需要装任何第三方库因为turtle是Python标准库自带的海龟绘图模块。你只要有一台装了Python的电脑打开任意一个能写Python的编辑器照着下面的思路一步步来就行。1. 项目整体思路把皮卡丘拆成几何块1.1 turtle库到底适合做什么很多初学者一听“turtle”就觉得这是小学生玩的东西画个正方形、画个五角星就完事了。这个评价对了一半turtle确实常出现在少儿编程启蒙里因为它的反馈很直观——你下一道指令屏幕上的小海龟就动一下留下一条线。但它的上限并不低坐标定位、角度计算、路径填充、函数封装、事件监听这些写正经程序会用到的概念它都能练到。拿画皮卡丘这件事来说它至少涉及三块能力一是把复杂图形拆解成基础几何形状的能力二是对平面坐标系的理解三是对绘制顺序和图层关系的判断。这三样能力放到任何图形编程里都是通用资产。所以与其说画皮卡丘是“玩”不如说这是一个训练图形思维的好项目。1.2 皮卡丘的特征拆解皮卡丘长什么样每个人心里都有数但要把它落到代码里就得先把它“肢解”成turtle能处理的元素。我一般会先找特征再按特征划分几何块。头部宽而圆润的黄色大脸头顶两侧各有一根长耳朵。耳朵左右对称耳尖是黑色的耳身是黄色。眼睛两颗黑色圆点位置在脸部靠上。腮红两团红色圆斑位置在眼睛斜下方两侧。嘴巴一个小倒三角形或弧形低调地挂在鼻子下面。身体比头小一圈的黄色椭圆体手脚短粗。尾巴从身体一侧伸出来顶端有明显的闪电形状。这么一拆皮卡丘就不再是一张复杂的图片而是“几个圆、几个多边形、几条弧线”的组合。后续我们要做的就是在turtle坐标系里把这些几何块按顺序画出来再填充对应的颜色。2. 环境准备先确保turtle能用起来2.1 快速检查Python与turtle环境正式写代码之前先花两分钟确认环境。很多人兴冲冲复制了代码跑起来却报错“No module named turtle”或者窗口一闪而过问题大多出在环境这一层。打开命令行工具Windows系统按Win R输入cmd回车macOS或Linux系统直接打开终端。然后逐条执行下面的命令python --version python -c import turtle; print(turtle ok)第一条用来确认Python版本第二条用来确认turtle模块能否正常加载。如果出现turtle ok说明环境没问题直接跳到第三节如果第一条命令提示找不到python说明Python没装或者没有把Python加入环境变量如果第二条命令报ModuleNotFoundError那就要看看你是不是装了某个精简版Python发行包或者电脑里同时装了多个版本导致解释器指向混乱。2.2 编辑器怎么选这不是what to use的安利环节我直接给结论。如果你是刚接触Python用Python自带的IDLE就够了它的优点是零配置打开后直接写代码、按F5运行turtle窗口能正常弹出。如果你不想被自带IDE的简陋界面劝退VS Code也是好选择装一个Python扩展按CtrlF5运行代码一样没问题。如果你打算以后长期做Python开发那直接用PyCharm也行它的代码提示更完整但启动速度慢、内存占用大画个皮卡丘属于杀鸡用牛刀。编辑器适合人群注意点IDLE纯新手、零配置用户自带打开即用按F5运行VS Code想兼顾未来开发的人需要装Python插件并选择正确的解释器PyCharm长期Python开发者功能全但重运行前要配置好项目解释器2.3 为什么turtle库“用不了”“turtle用不了”是我在教程类评论里看到最多的一个问题这里统一说明最常见的三种情况。第一种报错ModuleNotFoundError: No module named turtle。正常情况下turtle就在标准库里不需要额外安装。出现这个报错要么是Python环境不完整要么是你运行代码的解释器和装Python的解释器不是同一个尤其是在Linux系统上系统自带的python3和后来装的Python版本容易混淆。建议用where python或which python3看一下当前解释器路径确认你到底在用哪一套环境。第二种报错TclError: no display name and no $DISPLAY environment variable。这个常见于Linux服务器或没有图形界面的容器环境里跑turtleturtle需要图形窗口才能工作纯命令行环境跑不了。第三种运行后窗口一闪而过。这通常是因为代码里没有turtle.done()或turtle.mainloop()画完图形后程序立刻结束窗口就被系统回收了。后面我会在代码里加上这个关键行。3. 坐标规划画之前先在纸上“打点”3.1 turtle坐标系基础turtle的坐标系其实很简单窗口中央是原点 (0, 0)水平方向是x轴向右为正垂直方向是y轴向上为正。默认情况下窗口大小和坐标范围会影响你能画的区域比如我们用turtle.setup(800, 600)把窗口设为800像素宽、600像素高那x大致就是-400到400y大致是-300到300。这个坐标系就是我的“画布地图”。每次我动笔前都会先确定要画的图形占据哪些坐标范围再决定从哪个点开始落笔。养成这个习惯之后你会发现turtle画图的最大难点不在语法而在“你脑子里有没有一张明确的地图”。3.2 皮卡丘的结构分解与坐标参考我这里给出一组参考坐标不是定死的你可以按自己的审美去调。它代表的是“我把皮卡丘的每个部位放在画布的什么位置”。头部整体在y轴上方大致范围是x从-120到120y从-10到200顶部到(0, 200)。左耳从头部左上侧向上延伸三个关键点大概是(-105, 160)、(-160, 270)、(-70, 230)。右耳与左耳镜像对称大概是(105, 160)、(160, 270)、(70, 230)。身体在头部下方y从20往下到-190左右宽到±125。尾巴从身体右侧往外伸用一组折线画出闪电尖角。眼睛、腮红都是小圆眼睛在x±45、y110附近腮红在x±80、y70附近。我第一次画的时候这些坐标全是靠“估计重复调试”磨出来的不是一次到位。你先按参考坐标画出来如果觉得耳朵太歪、脸太窄就手动改两个数字再跑一遍慢慢调到你满意的样子。3.3 为什么用“轮廓填充”而不是一笔一画新手最容易做的事是让海龟从头到脚“描”皮卡丘的轮廓像手绘一样一笔一笔来。这种方式不是不行但效率很低而且turtle填充颜色需要封闭路径一笔一画地描很容易在最后一个闭合处出现空隙导致填充色漏出去。所以我选择用“定义坐标点列表连成多边形然后填充”的方式。把每个部位看成一组有序的顶点海龟从第一个顶点开始依次走到最后一个顶点再回到起点形成一个闭合多边形接着用begin_fill()和end_fill()包住整个绘制过程。这样代码结构清晰而且以后想画别的角色复用这套函数非常方便。4. 核心代码实现一套能跑出皮卡丘的方案下面这段代码是我简化过的版本控制在100行上下保留了皮卡丘的辨识度又不至于让坐标列表长得吓人。你可以直接复制运行也可以照着它自己改坐标。4.1 初始化画布与画笔参数import turtle def go(t, x, y): t.penup() t.goto(x, y) t.pendown()go函数是很多turtle项目的地基。它的作用是把海龟“拿起来”移动到指定坐标再“放下去”准备画线。如果不做这一步海龟移动的时候会拖出一条莫名其妙的线坐标规划就全乱了。4.2 封装两个核心绘图函数def draw_polygon(t, points, fill_color#F6D53B): t.color(black, fill_color) go(t, points[0][0], points[0][1]) t.begin_fill() for x, y in points[1:]: t.goto(x, y) t.goto(points[0][0], points[0][1]) t.end_fill() def draw_circle(t, x, y, radius, fill_color, pensize2): t.pensize(pensize) go(t, x, y) t.color(black, fill_color) t.begin_fill() t.setheading(0) t.circle(radius, 360) t.end_fill()draw_polygon接收一个坐标列表顺序连线并填充。draw_circle负责画眼部、腮红这类圆形元素。两个函数用颜色参数和坐标参数就可以随意复用。这里的setheading(0)是让海龟朝向x轴正方向保证圆从底部开始逆时针画否则圆的起始点会随海龟当前朝向变化位置容易失控。4.3 绘制头部与耳朵先画大块再画细节def draw_pikachu(): t turtle.Turtle() t.speed(0) t.pensize(2) head_points [ (-120, 80), (-100, 150), (-50, 190), (0, 200), (50, 190), (100, 150), (120, 80), (110, 20), (0, -10), (-110, 20) ] draw_polygon(t, head_points, #F6D53B) left_ear [(-105, 160), (-160, 270), (-70, 230)] draw_polygon(t, left_ear, #F6D53B) left_ear_tip [(-150, 245), (-160, 270), (-125, 235)] draw_polygon(t, left_ear_tip, #202020) right_ear [(105, 160), (160, 270), (70, 230)] draw_polygon(t, right_ear, #F6D53B) right_ear_tip [(150, 245), (160, 270), (125, 235)] draw_polygon(t, right_ear_tip, #202020)头部我用了10个顶点的多边形近似一个椭圆。耳朵则是三角形和头部共用一部分重叠区域保证连接处没有空隙。注意顺序先画整个头再画左右耳最后在每只耳朵尖端补一个黑色小三角形作为耳尖。这就是“先大块后细节”的体现否则细节画完了大块图形一填充细节就被盖住了。皮卡丘的经典配色需要提一下身体主色是黄色我用的十六进制色值是#F6D53B耳尖是接近黑色的#202020腮红用#E85D75。如果你觉得这个黄色偏暗或偏亮可以打开任意一个调色板工具换成你喜欢的黄。4.4 绘制身体与尾巴图层顺序可以反过来想body_points [ (90, 20), (120, -30), (125, -100), (90, -170), (60, -190), (-60, -190), (-90, -170), (-125, -100), (-120, -30), (-90, 20) ] draw_polygon(t, body_points, #F6D53B) tail_points [ (120, -80), (160, -80), (150, -110), (200, -120), (145, -150), (160, -180), (110, -130) ] draw_polygon(t, tail_points, #F6D53B) tail_base_points [(120, -80), (130, -100), (110, -130)] draw_polygon(t, tail_base_points, #8B5A2B)身体部分依然用多边形近似尾巴则是带折角的闪电形状。这里讲一个图层顺序的经验turtle的填充是不透明的后画的图形会覆盖先画的图形。所以你要反过来想如果有哪个色块应该在其他色块“下面”就得先画它。上面代码里有个小细节——尾巴根部用棕色小三角形做了一个“连接块”这个棕色块会直接盖在黄色身体的一部分上形成皮卡丘尾巴根部的过渡区域。这个效果就是刻意用后画覆盖先画实现的。4.5 面部细节眼睛、腮红与嘴巴draw_circle(t, -45, 110, 12, #202020) draw_circle(t, 45, 110, 12, #202020) draw_circle(t, -80, 70, 15, #E85D75) draw_circle(t, 80, 70, 15, #E85D75) go(t, -4, 95) t.setheading(-90) t.pensize(3) t.color(black) t.forward(10) go(t, -25, 80) t.setheading(-60) t.color(#202020) t.pensize(3) t.circle(30, 120) go(t, 0, 92) t.setheading(-120) t.circle(30, -120) t.hideturtle() turtle.done()眼睛和腮红用4.2的draw_circle函数就能完成。鼻子我直接画了一条短竖线嘴巴用两个对称的圆弧拼出来。t.circle(30, 120)不是画完整圆而是让海龟沿半径30的圆走120度所以这是一段弧线适合勾勒卡通嘴型。turtle.done()必须放在最后它的作用是进入主事件循环让窗口保持打开。少了这一行程序执行完就会自动关闭窗口。4.6 完整代码汇总整段代码合在一起就是下面这样把前面的片段拼起来即可运行。import turtle def go(t, x, y): t.penup() t.goto(x, y) t.pendown() def draw_polygon(t, points, fill_color#F6D53B): t.color(black, fill_color) go(t, points[0][0], points[0][1]) t.begin_fill() for x, y in points[1:]: t.goto(x, y) t.goto(points[0][0], points[0][1]) t.end_fill() def draw_circle(t, x, y, radius, fill_color, pensize2): t.pensize(pensize) go(t, x, y) t.color(black, fill_color) t.begin_fill() t.setheading(0) t.circle(radius, 360) t.end_fill() def draw_pikachu(): t turtle.Turtle() t.speed(0) t.pensize(2) head_points [ (-120, 80), (-100, 150), (-50, 190), (0, 200), (50, 190), (100, 150), (120, 80), (110, 20), (0, -10), (-110, 20) ] draw_polygon(t, head_points, #F6D53B) left_ear [(-105, 160), (-160, 270), (-70, 230)] draw_polygon(t, left_ear, #F6D53B) left_ear_tip [(-150, 245), (-160, 270), (-125, 235)] draw_polygon(t, left_ear_tip, #202020) right_ear [(105, 160), (160, 270), (70, 230)] draw_polygon(t, right_ear, #F6D53B) right_ear_tip [(150, 245), (160, 270), (125, 235)] draw_polygon(t, right_ear_tip, #202020) body_points [ (90, 20), (120, -30), (125, -100), (90, -170), (60, -190), (-60, -190), (-90, -170), (-125, -100), (-120, -30), (-90, 20) ] draw_polygon(t, body_points, #F6D53B) tail_points [ (120, -80), (160, -80), (150, -110), (200, -120), (145, -150), (160, -180), (110, -130) ] draw_polygon(t, tail_points, #F6D53B) tail_base_points [(120, -80), (130, -100), (110, -130)] draw_polygon(t, tail_base_points, #8B5A2B) draw_circle(t, -45, 110, 12, #202020) draw_circle(t, 45, 110, 12, #202020) draw_circle(t, -80, 70, 15, #E85D75) draw_circle(t, 80, 70, 15, #E85D75) go(t, -4, 95) t.setheading(-90) t.pensize(3) t.color(black) t.forward(10) go(t, -25, 80) t.setheading(-60) t.color(#202020) t.pensize(3) t.circle(30, 120) go(t, 0, 92) t.setheading(-120) t.circle(30, -120) t.hideturtle() turtle.done() if __name__ __main__: draw_pikachu()运行之后你会看到一个简化版的皮卡丘轮廓。第一次跑可能和原版差距比较大不要着急turtle画图本身就是“调参游戏”你改一个坐标、换一个颜色效果就完全不一样。5. 常见问题与排查技巧实录5.1 画布窗口一闪而过这个我在2.3里提到过是最常见的问题。解决办法是在代码末尾加上turtle.done()或turtle.mainloop()。如果你是照着上面完整代码运行的已经加了这行就不会闪退。如果你在IDLE之外的其他编辑器运行记得确认代码确实执行到了最后一行而不是中途报错退出。5.2 图形超出画布范围如果运行后你发现皮卡丘的耳朵尖或尾巴跑到窗口外面去了说明你的坐标范围超出了默认画布大小。可以先在开头执行turtle.setup(800, 600)把画布明确设为800×600像素。如果还不够再大一点也行比如setup(1000, 800)。注意setup设置的是窗口尺寸而不是坐标范围。坐标范围默认跟随窗口尺寸变化窗口扩大能显示的区域就扩大。5.3 填充颜色盖住了后面的图形turtle填充是不透明的。你辛辛苦苦画好背景一填充背景没了或者辛辛苦苦画好前面的细节一填充细节没了。所以绘制顺序必须按“从后到前”排列。我画皮卡丘的时候先画头和身体这些大面积色块再画眼睛、腮红这些细节最后画嘴巴就是这个原因。如果你把腮红先画再画头部腮红就会被黄色覆盖掉。5.4 常见报错与解决方案速查报错信息原因解决方案ModuleNotFoundError: No module named turtle解释器环境异常或Python安装不完整检查解释器路径换用官方完整版PythonTclError: no display name and no $DISPLAY在没有图形界面的环境运行本地电脑运行或用带图形界面的环境TypeError: circle() takes ...弧线参数写错检查t.circle(radius, extent)的参数类型窗口一闪而过缺少事件循环代码末尾加turtle.done()图形重叠覆盖绘制顺序不对先画被遮挡的底层图形再画上层图形5.5 调试坐标的土办法坐标调不准最笨也最有效的方法是通过t.goto()导航过去看看海龟停在哪然后决定这个点要不要调整。更省事的方法是在绘图函数里临时把t.speed(0)改成t.speed(5)让绘制过程肉眼可见你就能看到海龟是从哪里开始偏的。我调坐标的时候还会在关键位置用t.dot(5, red)画一个红点作为标记这样就知道那个顶点到底落在哪里了定位完再删掉这些调试行。6. 优化思路从“能画”到“画得更好”6.1 用对称减少重复坐标皮卡丘的左右耳、左右眼、左右腮红基本都是镜像关系。你可以写一个小函数输入一个x坐标就自动镜像成负值def mirror_point(p): return (-p[0], p[1]) def mirror_points(points): return [mirror_point(p) for p in points]然后只要定义右侧的坐标列表左侧直接用mirror_points生成代码量能少三分之一。这个思路在工作里也常用凡是左右对称的UI元素都可以用类似方法减少重复。6.2 让皮卡丘动起来turtle除了画静态图还能做简单的动画。入门做法是先画一帧清屏再移动坐标画下一帧。比如你可以让皮卡丘的耳朵上下摆动turtle.tracer(0) # 关闭逐帧刷新 turtle.update() # 手动刷新画面然后循环里修改耳朵尖的y坐标重绘整个图形再用time.sleep(0.05)控制帧间隔。这样皮卡丘看起来就像在点头或摇耳朵。如果想让交互更有趣还可以用turtle.onkeypress()绑定键盘事件按方向键让皮卡丘移动。6.3 保存绘制结果turtle窗口本身没有“保存为图片”的按钮但有两种常用办法。一种是对窗口直接截屏另一种是使用PostScript导出ts turtle.getcanvas() ts.postscript(filepikachu.ps)生成的是.ps文件需要用Ghostscript或在线转换工具转成PNG。如果只想快速分享直接截屏是最省事的。需要注意的是postscript导出的是矢量线条和填充区域转PNG时如果坐标里有中文字体或特殊字体可能显示异常皮卡丘这种纯色块图形基本没这个问题。6.4 从皮卡丘迁移到其他角色等你把这套“拆解几何块→坐标列表→填充函数”的流程跑熟了再画其他卡通角色基本就是把坐标列表换一套的事。杰尼龟、小火龙、可达鸭它们的结构无非是头、身体、四肢、五官、标志性配件。我一直觉得turtle项目的价值不在于它画出来的图有多精细而在于它逼着你去分析一个物体的构成然后用有限的绘图原语去表达它。这种抽象能力才是做图形编程最核心的东西。说句实话第一次完整跑出这个皮卡丘时我盯着屏幕上那个线条粗糙、比例略微走样的黄色小家伙看了半天还是觉得挺满意的。后来我把坐标重新梳理了一遍加了对称函数又给它做了个摇耳朵的小动画发现同样的框架能玩出的花样远比你最初设想的要多。如果你也想动手试试建议别急着复制我的整段代码先拿纸笔大概定一下皮卡丘的头在哪个范围、耳朵尖在哪、尾巴朝哪边再回来看代码你会更容易理解每一组数字在干什么。画得像不像不重要能把想法变成屏幕上的图像这一步迈出去你就已经比昨天的自己进步一截了。