简介:一份面向Python初学者、编程爱好者及信息技术教师的趣味绘图实例,借助turtle库绘制圣诞树及星星、圣诞帽等节日装饰元素,既能用于圣诞节主题的编程练习,也可作为课堂讲解循环与函数的教学案例。压缩包内仅含1个Python脚本,大小约2KB,代码量精简,适合逐行阅读、运行和二次修改。该资源已有12754人学习下载,热度较高,适合作为入门阶段的参考代码。脚本通过三角形层叠构成树冠、旋转重复绘制五角星、红白配色填充圣诞帽,清晰展示了turtle库的移动、转向、颜色设置等核心命令;同时利用for循环与函数封装体现参数传递和代码复用思想,引入time库延时让绘制过程产生动画效果,还可以扩展鼠标交互来增加趣味。对于想快速体验Python图形绘制或制作节日创意程序的学习者而言,这份代码能帮助理解基本绘图原理,并能轻松改造成个性化的圣诞主题作品。
1. turtle 圣诞树:从 zip 解压到海龟上屏
冬令时下班回家,在技术群里看到有人丢了一个python圣诞树绘制代码.zip,解压后是一个孤零零的Christmas tree.py。运行起来,黑色画布上一只小海龟开始转圈,先是绿色三角形一层层往上摞,接着树顶冒出五角星,最后连帽子和彩球都出现在屏幕上。这个项目看起来是节日玩票,实际是一个浓缩的 Python 图形编程样本:坐标系、角度方向、填充规则、循环画线、事件循环,全在一百多行代码里。它适合刚学完for循环和函数定义的初学者,也适合想快速验证 turtle 能力边界的开发者——比如我在里面就踩了几个画布坐标和角度方向的坑。下面从环境准备开始,把整个绘制链路拆开。
2. 环境准备与 turtle 的坐标系和画笔模型
2.1 zip 解压与运行环境
turtle是 Python 标准库,不需要pip install,这一点让 zip 里的脚本拿到任何一台装好 Python 的机器上都能跑。解压命令在不同系统下略有差异:
# Linux / macOS unzip python圣诞树绘制代码.zip cd python圣诞树绘制代码 # Windows 建议直接右键解压,然后在解压目录打开 PowerShell python Christmas\ tree.py注意 zip 里的文件名大概率带空格,命令行直接写Christmas tree.py会被解析成两个参数,所以要么用反斜杠转义,要么解压后重命名为tree.py。我一般会先重命名再运行,少很多麻烦。
运行前确认python命令可用。Windows 上如果提示“找不到命令”,通常是安装时没有勾选 Add Python to PATH,去系统环境变量手工补上 Python 安装目录和Scripts子目录即可。Linux 上多数发行版默认只装 Python 3,命令是python3,直接跑python3 Christmas\ tree.py。
2.2 画布坐标系与海龟朝向
turtle 的绘图模型可以理解为一个带笔的箭头在画布上移动。它有两个核心状态:位置和朝向。位置用直角坐标(x, y)表示,原点在画布中心,向右为 x 正方向,向上为 y 正方向。朝向用角度表示,0 度指向正右方,90 度指向正上方,180 度指向正左方,270 度指向正下方,逆时针方向增长。
下面这张表是绘制圣诞树时最高频使用的 API,建议贴在屏幕边上:
| 方法 | 作用 | 关键参数 |
|---|---|---|
forward(d)/fd(d) | 沿当前朝向移动 d 像素并画线 | d 是整数或浮点数 |
backward(d)/bk(d) | 沿当前朝向的反方向移动 | 等价于forward(-d) |
left(a)/right(a) | 相对当前朝向旋转 a 度 | a 是角度,不是弧度 |
setheading(a) | 绝对朝向,直接设为 a 度 | 0=东 90=北 180=西 270=南 |
penup()/pendown() | 抬笔/落笔 | 抬起时移动不留痕 |
color(c)/fillcolor(c) | 设置画笔色/填充色 | 支持'green'或'#228B22' |
begin_fill()/end_fill() | 标记填充区域 | 必须成对出现 |
goto(x, y) | 移动到指定坐标 | 会画线,需配合penup |
speed(s) | 设置绘制速度 | 1-10,0表示最快 |
tracer(n) | 每 n 次刷新一次屏幕 | 0关闭自动刷新 |
update() | 手动刷新画布 | 配合tracer(0)使用 |
mainloop() | 进入事件循环,保持窗口 | 必须调用,否则窗口闪退 |
新手最容易搞混的是left和setheading的差别:left(90)是在当前朝向基础上逆时针转 90 度,而setheading(90)是强制把朝向设为正上方。画正多边形时用left更自然,画固定几何形状时用setheading更可控。圣诞树代码里两种方式都会出现,建议都熟悉。
2.3 最小绘制模板
在动手画树之前,先写一个最小模板验证环境是否正常:
import turtle screen = turtle.Screen() screen.setup(800, 600) # 画布宽 800 高 600 screen.bgcolor("black") # 深色背景,圣诞树常用 pen = turtle.Turtle() pen.speed(3) pen.color("green") pen.penup() pen.goto(0, -200) # 移动到底部中间,不画线 pen.pendown() pen.setheading(60) # 绝对朝向:左上 pen.forward(120) pen.hideturtle() screen.mainloop() # 保持窗口打开这段代码验证了三件事:画布尺寸是否正常、颜色是否能渲染、事件循环是否工作。screen.setup(800, 600)设置的是窗口逻辑尺寸,不是像素分辨率,缩放窗口不影响逻辑坐标。pen.hideturtle()把箭头隐藏,只保留绘制轨迹,视觉效果干净很多。最后的screen.mainloop()是必须的,不调用它,程序执行完会立刻关闭窗口,在 IDE 里跑尤其明显。
3. 圣诞树主体:三角形层叠与填充绘制
3.1 树冠层叠的设计思路
圣诞树的主体在逻辑上可以拆成两部分:树干和树冠。树干是一个棕色的矩形,树冠是若干个绿色三角形从下往上叠加。层与层之间,宽度逐渐缩小,颜色深浅也可以逐层变化,制造出立体感。
为什么用三角形叠加而不是直接画一个大三角形?因为三角形叠加可以通过改变每层的底边宽度和垂直间距,模拟出树枝分层的视觉效果。层数越多,树的轮廓越接近真实的松树形状。zip 里的原始代码大概率只是两层或三层三角形,但把层数参数化之后,扩展到十层也只是改一个数字的事。
3.2 绘制一层三角形
先封装单层三角形绘制函数,这是整个树冠的基础:
def draw_triangle(turtle, width, y_pos, color): """以底边中点为锚点,画一个等边三角形""" turtle.penup() turtle.goto(-width / 2, y_pos) # 左下角 turtle.pendown() turtle.fillcolor(color) turtle.begin_fill() turtle.setheading(0) # 朝正右方 turtle.forward(width) # 画底边 turtle.left(120) # 转向左上 turtle.forward(width) # 画左斜边 turtle.left(120) # 转向左下 turtle.forward(width) # 画右斜边,回到起点 turtle.end_fill()函数的前两个参数width和y_pos决定了三角形的大小和位置。goto(-width / 2, y_pos)将海龟移动到三角形左下角,然后setheading(0)确保底边水平。三次forward配合两次left(120)恰好走完等边三角形的三条边,第三次forward后海龟会回到起点。注意这里必须用left(120)而不是left(60),因为三角形外角是 120 度,很多人在这一步转反,画出奇怪的凹多边形。
3.3 层叠参数与颜色渐变
层叠树冠需要对每层指定不同的宽度和颜色。我把这些参数放在一个列表里集中管理:
# 每层:底边中点 y 坐标, 底边宽度, 填充颜色 tree_levels = [ (-150, 320, "#1B5E20"), # 最下层,深绿 (-90, 260, "#2E7D32"), (-30, 200, "#388E3C"), (30, 140, "#43A047"), (90, 80, "#66BB6A"), # 最上层,浅绿 ]每一层的 y 坐标间隔 60 像素,宽度比上一层收缩 60 像素,这样层与层之间有明显的阶梯感。树冠从下往上画:先画大三角形,再画小三角形,小三角形会覆盖在大三角形顶部,形成叠加效果。把 y 坐标调成负数,是为了把树的整体位置压到画布下半部分,给树顶的星星和帽子留出空间。
3.4 树干与完整主体代码
树干是一个简单的矩形,画在树冠最下层的中心位置。矩形不需要begin_fill以外的新技术,关键是四个角坐标要算准:
import turtle screen = turtle.Screen() screen.setup(800, 600) screen.bgcolor("white") pen = turtle.Turtle() pen.speed(5) # 树干:棕色矩形 pen.penup() pen.goto(-25, -200) pen.pendown() pen.fillcolor("#8D6E63") pen.begin_fill() pen.setheading(0) pen.forward(50) # 宽 50 pen.left(90) pen.forward(80) # 高 80 pen.left(90) pen.forward(50) pen.left(90) pen.forward(80) pen.end_fill() # 树冠:遍历层参数 tree_levels = [ (-150, 320, "#1B5E20"), (-90, 260, "#2E7D32"), (-30, 200, "#388E3C"), (30, 140, "#43A047"), (90, 80, "#66BB6A"), ] for y, width, color in tree_levels: draw_triangle(pen, width, y, color) pen.hideturtle() screen.mainloop()这里for y, width, color in tree_levels是典型的解包遍历,列表里每个元组对应一层的三个核心参数。树干底部放在 y = -200,树冠最下层底边在 y = -150,两者之间留出 50 像素的衔接带,视觉上树干会露在树冠下方。如果树冠底边直接覆盖树干顶部,看起来反而不自然。
参数调整时的经验是:树冠每层高度差保持在 50-80 像素,宽度收缩率在 15%-25% 之间比较自然。收缩率太大,树显得尖;太小,树显得圆。想要更高更瘦的树,可以增加tree_levels的项数,同时把坐标整体上移。
4. 星星与圣诞帽:五角星几何和装饰组合
4.1 五角星的绘制原理
树顶的星星是圣诞树项目里最考验几何直觉的部分。五角星的外角是 144 度,画法是循环五次“前进一段距离 + 右转 144 度”。为什么是 144 度?因为五角星每个顶点处,笔从一条边转到另一条边需要旋转的外角等于 180 度减去内角,正五角星的内角是 36 度,所以外角是 144 度。给代码:
def draw_star(turtle, size, x, y): """画一个五角星,中心大致在 (x, y)""" turtle.penup() turtle.goto(x, y) turtle.pendown() turtle.fillcolor("#FFD700") # 金色 turtle.begin_fill() turtle.setheading(0) for _ in range(5): turtle.forward(size) # 画一条边 turtle.right(144) # 外角 144 度 turtle.forward(size) turtle.left(72) # 内角 72 度 turtle.end_fill()注意这个循环里是“前进一次、右转、前进一次、左转”,而不是简单的五次旋转。五角星不是凸多边形,画完五条外边的同时需要交替转外角和内角,才能让五条边的走向交叉成星形。如果只用right(144)五次,画出来的是一个正五边形,不是星形。这是最常见的画星误区。
4.2 圣诞帽的绘制
摘要描述里提到圣诞帽由“梯形的底部加上一个三角形的顶部”组成。实战中更简洁的做法是:一个红色等腰三角形作为帽身,底部加一条白色矩形作为帽檐。这样视觉上就像一顶尖顶帽。把帽子放在树冠右侧或树顶旁边,避免和五角星重叠。
def draw_hat(turtle, x, y, cap_width): """画一顶红色圣诞帽,底部中心在 (x, y)""" height = cap_width * 1.2 turtle.penup() turtle.goto(x - cap_width / 2, y) turtle.pendown() # 红色帽身 turtle.fillcolor("#D32F2F") turtle.begin_fill() turtle.setheading(0) turtle.forward(cap_width) # 底边 turtle.left(120) turtle.forward(height) # 斜边 1 turtle.left(120) turtle.forward(height) # 斜边 2 turtle.end_fill() # 白色帽檐 turtle.penup() turtle.goto(x - cap_width / 2 - 8, y - 12) turtle.pendown() turtle.fillcolor("#F5F5F5") turtle.begin_fill() turtle.forward(cap_width + 16) turtle.right(90) turtle.forward(18) turtle.right(90) turtle.forward(cap_width + 16) turtle.right(90) turtle.forward(18) turtle.end_fill()cap_width参数将帽子的尺寸与树的宽度关联起来,如果树最上层宽度是 80 像素,那么帽宽取 40 像素左右比例才协调。帽身先用left(120)画出一个等边三角形,帽檐稍微超出帽底两侧各 8 像素,形成包裹感。帽檐的矩形画完后,海龟朝向会停在初始方向的左侧,下一次调用setheading之前不要依赖当前朝向。
4.3 随机彩球的点缀
整棵树只有绿、棕、金、红四色会显得单调。给树冠随机撒一些彩色小球,能明显提升节日感。用random模块生成树冠区域内的随机坐标:
import random def draw_ball(turtle, x, y, radius=8): """在指定位置画一个彩色圆""" turtle.penup() turtle.goto(x, y - radius) turtle.pendown() turtle.fillcolor(random.choice(["#E53935", "#FB8C00", "#FDD835", "#8E24AA", "#1E88E5"])) turtle.begin_fill() turtle.circle(radius) turtle.end_fill() # 在树冠范围内随机撒 20 个彩球 for _ in range(20): draw_ball(pen, random.randint(-140, 140), random.randint(-140, 90))turtle.circle(radius)默认以当前点为圆的最下方顶点,从底部开始逆时针画圆。因此goto(x, y - radius)后,圆心的 y 坐标实际上约等于y。这里的 x 范围 ±140 和 y 范围 -140 到 90,是参照第 3 节那棵树的坐标系统手填的近似边界。如果树冠尺寸变了,这里要同步调整,否则彩球会飘到树外面。
5. 动态绘制与鼠标交互:time.sleep 和 onscreenclick
5.1 逐层绘制动画节奏
静态绘制一次性出图只适合验收,做节日展示时更希望看到海龟一步步画出整棵树。最直接的控制手段是time.sleep和turtle.speed搭配。我在每层三角形画完之后插入一个延时,让树冠像真实生长一样向上展开:
import time for y, width, color in tree_levels: draw_triangle(pen, width, y, color) time.sleep(0.4) # 每层间隔 0.4 秒这里有个需要注意的细节:如果使用了turtle.tracer(0)关闭自动刷新,所有绘制动作会累积在缓冲区,time.sleep只是暂停程序,画面并不会刷新,看起来就像卡住了一样。手动控制动画的正确方式是绘制后调用screen.update(),再sleep。两者配合,才能达到“画完一层、停一下、再画下一层”的效果。
5.2 鼠标点击事件给树上装点
turtle支持简单的鼠标事件监听。screen.onscreenclick(func)会在画布被点击时把点击坐标传给func(x, y)。利用这个机制,可以实现“点击哪个位置,就在哪里出现一颗小星星”的互动:
def plant_star(x, y): """点击画布时,在点击位置画一颗小星星""" draw_star(pen, size=15, x=x, y=y) screen.onscreenclick(plant_star)onscreenclick绑定的函数不要加括号,它只需要函数对象,事件循环会在每次点击时自动调用并传入坐标。注意坐标x, y是 turtle 逻辑坐标,不是屏幕像素坐标,窗口拖动或缩放后,逻辑坐标和物理坐标会存在偏移。
5.3 完整交互版 Demo
把绘制树、点击添加装饰、按键退出全部组合进一个完整脚本:
import turtle import random import time screen = turtle.Screen() screen.setup(800, 600) screen.bgcolor("black") screen.title("Python 圣诞树 - 点击添加星星") pen = turtle.Turtle() pen.speed(4) def draw_star(t, size, x, y): t.penup() t.goto(x, y) t.pendown() t.fillcolor("#FFD700") t.begin_fill() t.setheading(0) for _ in range(5): t.forward(size) t.right(144) t.forward(size) t.left(72) t.end_fill() def draw_tree(): """绘制完整的圣诞树""" # 树干 pen.penup() pen.goto(-25, -220) pen.pendown() pen.fillcolor("#8D6E63") pen.begin_fill() pen.setheading(0) pen.forward(50) pen.left(90) pen.forward(70) pen.left(90) pen.forward(50) pen.left(90) pen.forward(70) pen.end_fill() draw_star(pen, 30, 0, 120) # 树顶星星 time.sleep(0.3) # 树冠多层叠加 layers = [ (-140, 300, "#1B5E20"), (-80, 240, "#2E7D32"), (-20, 180, "#388E3C"), (40, 120, "#43A047"), ] for y, w, c in layers: pen.penup() pen.goto(-w / 2, y) pen.pendown() pen.fillcolor(c) pen.begin_fill() pen.setheading(0) for _ in range(3): pen.forward(w) pen.left(120) pen.end_fill() time.sleep(0.4) def add_random_star(x, y): """点击屏幕任意位置,在该处画一颗小星星""" draw_star(pen, 15, x, y) screen.onscreenclick(add_random_star) draw_tree() pen.hideturtle() screen.mainloop()这段代码把树顶的星星也挪到draw_star函数里,通过size=30放大显示。onscreenclick绑定的回调会先于draw_tree()生效,但因为绘制树需要几秒钟,用户会在绘制完成后的交互阶段点击画布,不会有冲突。time.sleep控制树冠逐层出现,配合tracer默认刷新,就能看到海龟的绘制轨迹。
6. 参数中心化、动画提速与常见报错处理
6.1 把所有魔法数字收进一个配置区
把树冠层数、颜色、尺寸、动画间隔全部集中到一个字典里,是把这个玩具项目变成工程化代码的第一步。这样做的好处是,别人拿到 zip 后不需要读完整段代码,只看配置区就能知道怎么调尺寸和颜色:
CONFIG = { "canvas": (800, 600), "bg": "black", "trunk": {"width": 50, "height": 70, "color": "#8D6E63"}, "layers": [ {"y": -140, "width": 300, "color": "#1B5E20"}, {"y": -80, "width": 240, "color": "#2E7D32"}, {"y": -20, "width": 180, "color": "#388E3C"}, {"y": 40, "width": 120, "color": "#43A047"}, ], "star": {"size": 30, "x": 0, "y": 120, "color": "#FFD700"}, "animation_delay": 0.4, } def draw_tree_from_config(): """根据 CONFIG 字典绘制圣诞树""" for layer in CONFIG["layers"]: draw_triangle(pen, layer["width"], layer["y"], layer["color"]) time.sleep(CONFIG["animation_delay"])这种做法的额外收益是:以后想用同一套代码画数据可视化中的堆叠图形,只需要改layers里的数值,绘制逻辑一行不用动。配置和逻辑分离,是 turtle 项目迈向更大图形项目的第一步。
6.2 用 tracer 与 update 手动控制刷新
默认 turtle 每画一笔就刷新一次屏幕,画几百个装饰球时会明显卡顿。我一般会这样提速:
turtle.tracer(0) # 关闭自动刷新 # ... 执行所有绘制 ... turtle.update() # 一次性刷新所有内容tracer(0)把所有绘制操作缓存起来,最后一次update()统一上屏,绘制速度会提升数倍。代价是绘制过程不可见,且time.sleep在tracer(0)下不会产生动画效果。如果既要动画又要速度,可以每隔一层手动update()一次,做到“逐层显示”:
turtle.tracer(0) for layer in CONFIG["layers"]: draw_triangle(pen, layer["width"], layer["y"], layer["color"]) turtle.update() # 手动刷新该层 time.sleep(0.3) turtle.update()6.3 高频报错与排查
turtle 项目的报错大多集中在坐标计算和事件循环上,把常见问题整理成表格方便对照:
| 现象 | 原因 | 处理 |
|---|---|---|
| 运行后窗口一闪而过 | 缺少mainloop()或done() | 在脚本末尾加screen.mainloop() |
TurtleGraphicsError: bad color sequence | 颜色字符串写法错误 | 检查颜色名或十六进制格式,如"#FFD700"必须带# |
| 树冠歪斜、线条交叉 | left角度用错或goto前忘了penup | 三角形外角固定是 120 度;移动前先抬笔 |
| 随机彩球飘出树外 | 随机坐标范围与树冠实际坐标不匹配 | 打印CONFIG["layers"]中的坐标边界,反推随机范围 |
| PyCharm 中书签模式下窗口不弹 | IDE 的特殊交互模式 | 直接命令行运行.py文件,不要用 Python Console |
| 点击事件不生效 | 绑定的函数有参数但写法不对 | onscreenclick只接收函数名,坐标由事件循环传入,不需要在绑定处调用 |
6.4 拿边界坐标验证绘制是否合理
最后给一个我常用的验证技巧:在绘制完每一层后,把当前海龟坐标打出来,对比预期值。pen.position()返回一个(x, y)元组,可以用来核对三角形的三个顶点是否落在预期区域内:
print(f"Layer {idx} - bottom-left: {(-layer['width']/2, layer['y'])}, " f"top: {pen.position()}")通过核对输出坐标和CONFIG里的配置值,可以快速定位是坐标算错还是绘制顺序有误。这个检查方法比盯着画布猜测要靠谱得多,尤其是随机装饰元素数量大时,一边打印坐标一边调整随机范围,能直接把定位问题的周期缩短一半。
本文还有配套的精品资源,点击获取