
简介一份基于 Python turtle 模块绘制中国龙的完整源代码项目面向 Python 初学者与图形编程教学场景无需安装第三方库即可运行适合在龙年海报、创意编程课或个人练习中使用。包内共 4 个文件带详细注释的 draw_dragon.py 是核心代码说明文档 md 梳理绘制思路并补充运行说明png 展示静态成品gif 动态演示海龟绘图过程压缩包约 720KB整体结构简洁、层次清晰便于对照学习。代码从初始化画笔大小、速度与颜色入手通过函数调用分步绘制龙头、身体、四肢和尾巴充分展示了循环结构组织重复图形、函数拆分绘制逻辑、参数调整视觉样式等关键技巧并配有逐段中文注释帮助理解执行过程。目前已有 1158 人学习下载是一份兼顾编程训练与文化创意的入门实践素材。 过完年开工那几天朋友圈里全是各种龙年海报。我盯着屏幕想天天写Python能不能用代码自己画一条龙出来不搞什么花哨的三维渲染就用Python自带的turtle库一笔一笔画一个“龙年吉祥物”。这件事听起来像是个小玩具但真做起来坐标换算、曲线设计、模块拆分一个都不少还挺能锻炼人的。这篇文章就把我调试了一晚上的完整源代码、详细代码注释和一些踩坑经验全部分享出来适合刚学Python的人练手也适合想在龙年给朋友送点“程序员式祝福”的人直接抄作业装上Python就能跑不需要安装任何第三方库。我一开始也想用matplotlib或者PIL画但试了一圈发现turtle才是干这活最顺手的。它的逻辑就是“一只笔在画布上走”哪条线该从哪下笔、往哪个方向弯、用什么颜色全都一目了然。尤其适合这种“一笔一笔画吉祥物”的场景。下面我从需求拆解开始讲再到完整代码最后把调试过程中最容易翻车的几个位置挨个捋一遍。1. 画龙之前先把思路顺清楚1.1 这个项目到底在画什么标题里写了“超酷超漂亮的中国龙”但说实话用turtle去画一条写实风格的龙既没必要也不现实。turtle最擅长的是几何图形、弧线、折线组合出来的“图案型”画面。所以我把目标定成一条Q版侧视龙龙头在左侧龙身像波浪一样往右延伸尾巴从右侧扬起身上铺满鳞片头顶两只分叉角嘴前两根飘舞的龙须背景再点缀几朵祥云。颜色上以金色、深红、米白为主一眼看上去就是春节那种热闹劲儿。这种风格的好处在于它不需要极高的美术功底只要把每个部件当成独立的几何形状来画就行。难度从高到低分别是龙身曲线、鳞片排列、龙头轮廓、龙角分叉、龙须走向、祥云填充。我在真正写代码之前先在一张纸上画了个简笔草图标清楚每个部件大致在哪个坐标区域这步非常关键。没有这张草图后面写代码就是拿着计算器硬试坐标试到天亮也未必能拼出一只龙。1.2 为什么是turtle而不是matplotlib或PILmatplotlib不是不能画但它天生是画图表的画曲线、散点很顺手画这种“由线组成的角色”就得反复构造路径对象代码绕来绕去。PIL倒是适合像素级创作但它的坐标体系、填充逻辑都比较底层对新手不友好。turtle的优势在于两点一是直接画笔走到哪里就画到哪里所见即所得二是有CPU实时动画能看到绘制过程特别有“手工感”。对于一个吉祥物项目来说过程本身比结果更有意思。有一点需要提前说明turtle是Python自带的库正常安装的Python环境里都有。但如果你的系统是精简版Linux比如某些Ubuntu服务器镜像可能会提示找不到turtle那是缺少tkinter这个GUI底层执行sudo apt install python3-tk就能解决。Windows和macOS的官方Python安装包一般没这问题。1.3 落笔之前先把构图定下来我的画布是1000×700坐标系原点在正中心。整条龙的大致布局我定成这样龙头中心在(-300, 80)龙身从(-220, 80)出发一路向右延伸经过大约6个波浪到(300, -30)附近龙尾再向右上方扬起祥云分别放在右上、左上和左下三个角落让画面平衡一些。这条龙的体量和位置确定之后剩下的工作就是往这个框架里填细节。这个框架的意义在于它让整个项目从“画画”变成“按坐标施工”。后面的每一个函数都在为这个框架服务。越是看起来自由的艺术创作越需要先把坐标锁死否则改一个参数就全部乱套。这也是我想通过这个项目传达的一个经验复杂任务的第一步永远是拆分和定位而不是追求一步到位。2. 核心细节解析与实操要点2.1 拆解龙的每一个部件拆解之后整个项目就是一组小函数的合集。我的模块划分是这样的draw_body绘制龙身主体一条描边加内芯的波浪线draw_scales沿龙身曲线排列半圆鳞片draw_tail绘制向上扬起的渐细尾巴draw_horns绘制两只分叉龙角draw_head绘制龙头轮廓、嘴和鼻子draw_face绘制眼睛、瞳孔和眼神高光draw_whiskers绘制两条龙须draw_cloud绘制背景祥云。这种拆分方式还有一层好处就是方便调试。如果龙角位置不对只需要单独修改draw_horns不需要在别人画好的几百行代码里翻来翻去。代码的可维护性在某些时候比“短小精悍”更重要尤其是当你打算把这个作品分享给同学、发到博客、让别人改着玩的时候。2.2 用一个函数生成波浪龙身龙身是全图最难画的部分。如果手工写几十个坐标点不仅累而且很难调出平滑的波浪。我的解决办法是用一个数学函数来生成所有坐标y base_y - drop wave。其中base_y是起点高度drop让龙身从左到右缓缓下沉wave用正弦函数制造上下起伏。def body_y(x): base 80 # 起点高度 drop (x - X0) * 18.0 / 100.0 # 每向右100px下沉18px wave 42 * math.sin((x - X0) * 2 * math.pi / 90.0) return base - drop wave用这个函数我只要把x从-220循环到300每隔6px取一个点用t.goto连起来就能得到一条非常平滑的波浪线。想调波浪的幅度就改42想调波浪密度就改90计算出来的每一点都能保证落在同一条曲线上。手写坐标做不到这种一致性。这也是全项目里最值得记住的一招凡是遇到“有规律的曲线”优先想数学表达式而不是想着一笔一笔抠坐标。2.3 颜色怎么搭配才喜庆中国龙的传统配色其实就是高对比度三件套金色、深红、米白。金色负责富贵感深红负责喜庆感米白负责留出呼吸空间。我实际用的色值是龙身内芯#F4B41A金色龙身描边#8B0000深红鳞片#B22222砖红龙头底色#F4B41A龙角#C17529棕金龙须#E74C3C橙红祥云#FDEBD0淡粉。这个组合放在米白色背景#FFF8E7上视觉层次比较清晰。这里有个细节值得注意龙身用一种颜色一条线画到底的话会显得很单薄。我用了两遍绘制第一遍用34像素宽的深红画线第二遍用24像素宽的金色画线等于给龙身加了一圈深红描边。描边这层“外轮廓”能把龙身和背景快速分离视觉分量立刻就不一样了。想让图案有立体感又不想做复杂渐变的时候粗线描边是最省力的办法。def draw_body(): # 1. 深红描边w宽度用34 t.pencolor(#8B0000) t.width(34) move(X0, body_y(X0)) for x in range(X0, X1 1, 6): t.goto(x, body_y(x)) # 2. 金色内芯宽度24刚好在红边内部 t.pencolor(#F4B41A) t.width(24) move(X0, body_y(X0)) for x in range(X0, X1 1, 6): t.goto(x, body_y(x))3. 完整源代码与逐段注释3.1 画布设置与三个最常用的工具函数在正式进入绘制之前我先把画布、画笔和三个工具函数准备好。setup控制窗口大小bgcolor设置背景颜色speed(0)是最高速度hideturtle把画笔箭头藏起来免得最后成品图里有一个烦人的小箭头。工具函数里move负责抬笔、移动、落笔避免每次都要写三行代码path_ellipse用来生成一个椭圆上的点序列这样画龙头、眼睛的时候就不用手工写坐标了fill_shape负责填充一个封闭多边形传入一系列坐标点、填充色和描边色即可。def move(x, y): t.penup() t.goto(x, y) t.pendown() def path_ellipse(cx, cy, rx, ry, n24, start0, end360): pts [] for i in range(n): ang math.radians(start (end - start) * i / (n - 1)) pts.append((cx rx * math.cos(ang), cy ry * math.sin(ang))) return pts def fill_shape(points, fill, outline): t.pencolor(outline) t.fillcolor(fill) t.begin_fill() move(points[0][0], points[0][1]) for x, y in points[1:]: t.goto(x, y) t.goto(points[0][0], points[0][1]) t.end_fill()3.2 龙身、鳞片与尾巴的绘制鳞片的画法是很典型的“重复中带规律”的活。沿着龙身曲线每隔14px取一个点画一个半径为8的半圆方向统一朝斜上方75度。这样铺出来的一排鳞片既整齐又有一种“正在爬动”的动态感。这里我一开始踩过坑让每个鳞片都严格垂直于曲线的切线方向结果发现曲线切线角度变化太快鳞片方向乱成一团。后来改成固定角度视觉效果反而更统一。龙尾的收尾有点特殊turtle的画笔宽度不能渐变所以我采用分段画法尾巴分成7段每段宽度逐渐递减从16一路减到1并且每段高度还按抛物线往上扬形成“龙尾甩起”的感觉。这种分段变宽法画尾巴、画树枝都很实用。def draw_scales(): t.pencolor(#B22222) t.width(2) for x in range(X0 18, X1 - 18, 14): y body_y(x) move(x - 6, y - 4) t.setheading(70) t.circle(8, 180) def draw_tail(): widths [16, 12, 9, 6, 4, 2, 1] t.pencolor(#8B0000) move(X1 6, body_y(X1)) for i in range(7): t.width(widths[i]) x X1 (i 1) * 26 y body_y(X1) (i 1) * 18 - 1.2 * (i 1) * (i 1) t.goto(x, y)3.3 龙头、龙角、眼睛和龙须龙头是整个项目里“手绘感”最强的部分。我用path_ellipse生成一个中心在(-300, 80)、横半径72、纵半径52的椭圆作为龙头主体填充成金色。嘴部用一个小一些、扁一点的半椭圆画在左侧鼻球用一个小椭圆压在嘴上。这样龙头正面看起来有一个可爱的“凸嘴”符合年画里龙的卡通化表达。龙角我用的是粗线分叉法每只角从头顶起笔先画一条主干向后上方走再在半路分出一条枝杈。龙须是两条带正弦波动的线从嘴的两侧向左下飘出。因为加了sin函数胡须会呈现出一种自然卷曲的弧度比手动写折线要灵活得多。def draw_horns(): t.pencolor(#C17529) t.width(8) # 左角 move(-295, 122) t.goto(-330, 178) t.goto(-355, 220) move(-310, 178) t.goto(-288, 222) # 右角 move(-255, 122) t.goto(-238, 178) t.goto(-222, 220) move(-260, 178) t.goto(-295, 222) def draw_whiskers(): t.pencolor(#E74C3C) t.width(3) move(-362, 52) for i in range(30): x -362 - i * 5 y 52 12 * math.sin(i * 0.35) - i * 0.8 t.goto(x, y) move(-350, 30) for i in range(30): x -350 - i * 5 y 30 10 * math.sin(i * 0.3 1.2) - i * 0.8 t.goto(x, y)眼睛的部分需要多说一句。龙的左眼我放在(-327, 90)右眼放在(-270, 90)两个眼睛都是“白色椭圆底黑色正圆瞳孔白色小圆高光”三层结构。瞳孔和眼白中心故意错开一点高光再往斜上方偏移一小段这样眼睛会显得有神而不是直勾勾地发呆。很多初学者画眼睛只画一个黑点画完总觉得“死气沉沉”其实就是缺了这层高光。3.4 祥云点缀与主流程入口祥云是背景气氛组我用了最简单的“三个圆加一个矩形底座”画法。三个圆错开排成一个云朵形状底部再用一个矩形填平颜色用淡粉色填充、浅橙色描边。云的尺寸、位置可以复用draw_cloud函数在代码最后调用三次分别放在三个角落。主流程入口写在ifname main里。用这种方式组织代码意思是只有直接运行这个文件时才会执行绘图流程。如果以后有人把函数import到自己的项目里就不会一导入就弹出画画窗口。if __name__ __main__: setup() draw_body() draw_scales() draw_tail() draw_head() draw_horns() draw_face() draw_whiskers() draw_cloud(-100, 200, 38) draw_cloud(120, 180, 30) draw_cloud(40, -180, 42) turtle.done()到这里我把完整代码也整理成了一个可以直接复制运行的版本所有注释都是中文方便对照理解。import turtle import math # 全局画笔与画布 t turtle.Turtle() screen turtle.Screen() X0, X1 -220, 300 # 龙身x轴范围 def setup(): screen.setup(1000, 700) screen.bgcolor(#FFF8E7) screen.title(Python Turtle 中国龙 - 龙年吉祥物) t.speed(0) t.hideturtle() turtle.delay(0) def move(x, y): 抬笔移动再落笔 t.penup() t.goto(x, y) t.pendown() def path_ellipse(cx, cy, rx, ry, n24, start0, end360): 生成椭圆上的点序列供fill_shape使用 pts [] for i in range(n): ang math.radians(start (end - start) * i / (n - 1)) pts.append((cx rx * math.cos(ang), cy ry * math.sin(ang))) return pts def fill_shape(points, fill, outline): 填充一个封闭多边形 t.pencolor(outline) t.fillcolor(fill) t.begin_fill() move(points[0][0], points[0][1]) for x, y in points[1:]: t.goto(x, y) t.goto(points[0][0], points[0][1]) t.end_fill() def body_y(x): 龙身曲线缓慢下沉 正弦波浪 base 80 drop (x - X0) * 18.0 / 100.0 wave 42 * math.sin((x - X0) * 2 * math.pi / 90.0) return base - drop wave def draw_body(): 深红描边 金色内芯的粗波浪线 t.pencolor(#8B0000) t.width(34) move(X0, body_y(X0)) for x in range(X0, X1 1, 6): t.goto(x, body_y(x)) t.pencolor(#F4B41A) t.width(24) move(X0, body_y(X0)) for x in range(X0, X1 1, 6): t.goto(x, body_y(x)) def draw_scales(): 沿龙身画半圆鳞片统一朝70度方向 t.pencolor(#B22222) t.width(2) for x in range(X0 18, X1 - 18, 14): y body_y(x) move(x - 6, y - 4) t.setheading(70) t.circle(8, 180) def draw_tail(): 分段变宽画出向上扬起的龙尾 widths [16, 12, 9, 6, 4, 2, 1] t.pencolor(#8B0000) move(X1 6, body_y(X1)) for i in range(7): t.width(widths[i]) x X1 (i 1) * 26 y body_y(X1) (i 1) * 18 - 1.2 * (i 1) * (i 1) t.goto(x, y) def draw_head(): 龙头主体 嘴 鼻子 points path_ellipse(-300, 80, 72, 52) fill_shape(points, #F4B41A, #8B0000) mouth path_ellipse(-368, 45, 20, 12, 24, -90, 110) fill_shape(mouth, #C0392B, #8B0000) pts path_ellipse(-350, 70, 9, 6) fill_shape(pts, #E67E22, #8B0000) def draw_horns(): 两只带分叉的龙角 t.pencolor(#C17529) t.width(8) move(-295, 122) t.goto(-330, 178) t.goto(-355, 220) move(-310, 178) t.goto(-288, 222) move(-255, 122) t.goto(-238, 178) t.goto(-222, 220) move(-260, 178) t.goto(-295, 222) def draw_face(): 双眼白色眼白 黑色瞳孔 白色高光 for ex, ey in [(-327, 90), (-270, 90)]: pts path_ellipse(ex, ey, 15, 13) fill_shape(pts, #FFFFFF, #000000) move(ex, ey - 2) t.fillcolor(#000000) t.begin_fill() t.circle(6) t.end_fill() move(ex 3, ey 2) t.fillcolor(#FFFFFF) t.begin_fill() t.circle(2) t.end_fill() def draw_whiskers(): 两条用正弦波动生成的龙须 t.pencolor(#E74C3C) t.width(3) move(-362, 52) for i in range(30): x -362 - i * 5 y 52 12 * math.sin(i * 0.35) - i * 0.8 t.goto(x, y) move(-350, 30) for i in range(30): x -350 - i * 5 y 30 10 * math.sin(i * 0.3 1.2) - i * 0.8 t.goto(x, y) def draw_cloud(cx, cy, r): 用三个圆和一个矩形拼出祥云 t.pencolor(#F5CBA7) t.fillcolor(#FDEBD0) t.width(2) circles [(cx - r, cy, r), (cx, cy r * 0.6, r * 1.2), (cx r, cy, r)] for x, y, rad in circles: move(x, y - rad) t.begin_fill() t.circle(rad) t.end_fill() move(cx - r * 1.5, cy - 0.4 * r) t.goto(cx r * 1.5, cy - 0.4 * r) t.goto(cx r * 1.5, cy 0.2 * r) t.goto(cx - r * 1.5, cy 0.2 * r) t.goto(cx - r * 1.5, cy - 0.4 * r) if __name__ __main__: setup() draw_body() draw_scales() draw_tail() draw_head() draw_horns() draw_face() draw_whiskers() draw_cloud(-100, 200, 38) draw_cloud(120, 180, 30) draw_cloud(40, -180, 42) turtle.done()4. 运行效果、调试过程与常见问题4.1 跑起来之后是什么效果代码运行后会先弹出画布然后你就能看到画笔从龙身开始一路画出金色波浪身体、红鳞片、尾巴、龙头、角、眼睛、龙须最后在角落浮现几朵祥云。整个过程在speed(0)下大概几秒完成如果电脑配置比较老可能稍微顿一下让它跑完就行。绘制结束后点一下窗口右上角的关闭按钮即可退出。实际效果是比较喜庆的一条Q版龙金色身体配深红描边在米白背景上很醒目。龙头因为是手工坐标拼的不会像AI生成那么精致但恰恰是这种手工感让它看起来更像“程序员亲手画出来的吉祥物”。如果送给朋友可以把最后的turtle.done()换成time.sleep(10)再在末尾加一句print(祝龙年大吉)做一个极客味十足的祝福程序。4.2 坐标调试实录一个老翻车的位置我必须坦白这版代码并不是我第一次写出来的。第一次画的龙身体和脑袋完全错位龙头跑到了身体波浪的下方鳞片也东倒西歪。排查了半天发现问题是龙身曲线用数学表达式生成而龙头坐标是拍脑袋填的两者根本没有互相验证。后来我把所有关键坐标点打印出来发现龙头椭圆中心(-300, 80)的右侧边缘大约在x-228而龙身起点在x-220正好能接上。这个细节确认之后龙头和身体才真正连成一体。这类问题的排查思路其实很通用当多个部件需要拼接时先检查衔接点坐标是否一致再检查曲线的起止范围。很多人改坐标是“看着不对就盲调”结果越调越乱。正确的做法是先把所有关键点坐标打印到控制台确认数学上对得上再去看视觉上顺不顺眼。逻辑正确了审美调整才有意义。4.3 turtle常见坑速查表我把这个项目里容易踩的坑整理成了表格也把网上被问得最多的问题一起放进来了。现象原因解决办法运行报错No module named turtle精简版Linux缺少tkinter安装python3-tkWindows/macOS一般不会遇到窗口一闪而过代码末尾没有turtle.done()加上turtle.done()让窗口保持画笔太快看不清过程speed(0)其实是最快想看动画就设置speed(1)到speed(10)绘制太慢图形点太多且每次更新屏幕用turtle.tracer(0)关动画最后turtle.update()一次性显示想保存成图片turtle没有直接保存png的接口用turtle.getcanvas().postscript(filedragon.eps)生成eps再转png代码里想注释一行用#开头比如# 这是注释不会影响运行Python环境问题系统装了多个Python在命令行输入python -V确认版本代码需要Python 3.4以上4.4 让画面再出彩的三个小技巧颜色随机是一个容易出效果的方向。把鳞片颜色从固定值改成从[#B22222, #D35400, #8E44AD]里随机选跑出来的龙鳞颜色每次都不一样收藏价值立刻提高。随机数记得用random.choice并且把随机种子留成可配置项。字体装饰也是不错的扩展方向。用turtle.write可以在画布上写文字设置字体为(SimHei, 36, bold)在龙的上方写上“龙年大吉”四个字整个画面会从图形作品升级成贺卡。坐标建议放在(-80, 230)左右具体位置可以微调。动画效果同样值得一试。用turtle的onclick绑定一个函数点击画布时让龙身重新换一种颜色或者让龙须的摆动幅度变大。虽然代码量会多一些但做出来的效果在朋友圈里绝对抢眼。我把这个留作一个思考题你可以先试试怎么把draw_whiskers里的sin幅度参数改成可调状态。最后再分享一个我自己的习惯每次调完参数我会立刻把效果截图保存下来文件名写成“dragon_v1”“dragon_v2”这种。因为常常会出现改动五个参数后反而觉得第一版最好看的情况。有历史版本可以回退才是真正高效的调参方式。编程画图这件事技术占一半审美和耐心占另一半。本文还有配套的精品资源点击获取