首页 / 资讯中心 / 文章详情

Python绘制玫瑰花图案:turtle与Matplotlib极坐标实战

Python绘制玫瑰花图案:turtle与Matplotlib极坐标实战 ★ FEATURED ARTICLE
上周帮朋友做一张电子贺卡我又把“Python趣味练习---绘制玫瑰花图案”这个经典题目翻了出来。网上搜一圈能看到的代码不少但真正能一次跑通的真不多有的缺了turtle.done()窗口一闪就没有的画到一半直接崩掉还有的画出来根本不是玫瑰像一团被压扁的番茄。把几种方案挨个试过之后我发现这件事拆开看其实特别有意思——有人用 turtle 一笔一笔画有人用 numpy 算坐标让数学公式成图还有人干脆用字符拼。它们目标相同但写出来的代码风格、应用场景、踩坑方式完全不一样。这篇文章就记录我完整折腾下来的思路和最终方案适合刚学完 Python 基础、想找个趣味练习练手的人也适合想用 Python 画点浪漫图形送给朋友的人。1. 三种常见“Python画玫瑰花”方案踩坑后我推荐这样选1.1 为什么“照着网上的代码抄”最容易翻车我一开始的做法很直接搜到一段 turtle 画玫瑰花的代码复制粘贴运行。结果第一个报错就是ModuleNotFoundError: No module named turtle。当时我还愣了一下turtle 不是 Python 自带的吗后来才发现问题出在文件名上——我把脚本命名成了turtle.pyPython 一启动就优先加载了我自己写的这个文件标准库的 turtle 反而进不来。这是新手最容易踩的坑之一。就算绕过了文件名冲突还有一堆问题等着你。比如网上不少代码用了turtle.mainloop()或turtle.done()但有些精简版本把收尾代码删了运行后画布窗口立刻关闭你根本看不清画了什么。再比如海龟默认画得很慢复杂玫瑰要画几百条线运行起来像放慢动作。这些都不是代码逻辑难而是“对绘图流程不熟悉”导致的。所以我的建议是别急着抄整段代码先把几种方案的原理摸清楚。你不需要懂高深的图形学只要知道“电脑怎么描述一笔一画”和“电脑怎么用一堆坐标点拼出曲线”这两件事后面就是拼积木。1.2 三种方案的优缺点对比我实际试下来最常见的画玫瑰方案就三类方案依赖库风格优点缺点turtle 逐笔绘图turtle标准库一笔一画有动画感直观、适合教学、能模拟手绘过程大图案绘制慢代码繁琐细节靠手工调坐标Matplotlib numpy 画玫瑰线matplotlib, numpy数学曲线成图精美线条流畅、可以精确控制数学参数需要理解极坐标编程思路比 turtle 抽象字符画 / 像素点阵方案PIL / 纯文本另类适合终端展示新鲜有趣、不依赖图形界面清晰度低颜色表现弱从学习价值看我推荐先玩 turtle再玩 Matplotlib。前者帮你建立“坐标、方向、角度、轨迹”的直觉后者帮你把同一个直觉用数学公式表达出来。两道都走过一遍你会发现在 Python 里“画图”其实是在和坐标系对话。1.3 我推荐的组合路线如果只让我保留一种方案我会选 Matplotlib numpy 的玫瑰线画法因为它出图快、可复制性强、视觉效果稳定。但如果你需要的是“像手绘一样慢慢画出玫瑰”的过程感那就用 turtle。这篇文章后面两部分会分别讲清楚你完全可以按自己的需求取舍。2. turtle版玫瑰花把“画一笔”翻译成代码2.1 先理解海龟绘图的三个关键概念turtle 的核心思路特别像小时候玩的那种小海龟机器人屏幕上有一只海龟你告诉它向前走、后退、左转、右转它移动时留下的轨迹就是线条。三个关键概念别搞混坐标、方向和角度。坐标默认画布中心是(0, 0)海龟初始位置就在这里方向朝右正东方向。方向用角度表示0 度朝右90 度朝上180 度朝左270 度朝下。旋转t.left(angle)是向左转t.right(angle)是向右转旋转时海龟的位置不移动。画玫瑰花最常用的 API 不是forward()而是circle()。t.circle(radius, extent)的意思是以当前位置为起点画一段半径为 radius、角度为 extent 的圆弧。extent 是 90 就画四分之一圆是 360 就画完整圆。这个函数是画花瓣的核心。2.2 用函数抽象一片花瓣用循环生成整朵花理解了circle()之后花瓣其实就是一个“由两段圆弧围成的封闭图形”。我常用的画法是import turtle t turtle.Turtle() t.speed(0) turtle.tracer(0, 0) # 关闭逐帧刷新加快绘制速度 t.color(red, hotpink) def petal(size60): t.begin_fill() for _ in range(2): t.circle(size, 90) t.left(90) t.end_fill() for _ in range(8): petal(60) t.left(45) turtle.update() turtle.done()拆开看petal()做的事情是先画一个半径 60、角度 90 的圆弧然后左转 90 度再画一个同样大小的圆弧。两段圆弧首尾相接中间会鼓出来形成一片花瓣的形状。begin_fill()和end_fill()表示这个封闭区域用颜色填充。外层循环每画一片花瓣就t.left(45)8 片花瓣会均匀绕一圈。你也可以改成 6 片left(60)想画更密集的花就改成 12 片left(30)。这个组合逻辑比记住一长串坐标要轻松得多。2.3 花茎和叶子的画法方向感比精确坐标更重要花茎不需要精确计算坐标只需要让海龟从花朵中心向下走。关键在于执行顺序先画花朵再画花茎这样海龟的起点正好在画布中心附近。t.penup() t.goto(0, 0) t.pendown() t.setheading(270) t.color(green) t.pensize(3) t.forward(150)setheading(270)的意思是直接把海龟方向设为朝下不需要慢慢旋转。叶子可以用同样的“两段圆弧”思路画把颜色换成绿色画完后用penup()移动到叶片位置再调整方向绘制。有个小技巧如果发现花茎被花瓣挡住了就把画花茎的代码放在画花瓣之后如果发现整体偏了用t.goto(x, y)微调别手工改一堆坐标。记住海龟绘图里“方向”是相对状态每次旋转都会改变下一次的起点所以 Debug 时先检查当前方向和位置再检查坐标值。2.4 让画面“活”起来的三个细节速度t.speed(0)是最快速度但只影响forward()、circle()这类移动操作不影响tracer()。更彻底的方法是turtle.tracer(0, 0)它会关闭逐帧动画画完整朵花后才一次性刷新。画复杂图案时速度能提升 10 倍以上。颜色t.color(red, hotpink)的第一个参数是边框色第二个是填充色。你可以改成pink、salmon或者用颜色值#FF6F91精细调节。叠层想画出一层一层的感觉可以循环三组不同半径和颜色的花瓣。例如外层用petal(80)、left(30)中层用petal(60)、left(45)内层用petal(40)、left(60)视觉层次立刻丰富起来。我实际画下来turtle 版本最大的价值不是“最终成品有多逼真”而是让你在调参的过程中建立起对图形构成的理解一片花瓣由两段弧组成一朵花由多片花瓣旋转组成一棵花茎由一条直线加几片叶子组成。这种“拆解组合”的能力玩转任何绘图库都好用。3. 数学画法才是成图利器极坐标玫瑰线与Matplotlib实战3.1 玫瑰线公式的底层直觉为什么 ra*cos(kθ) 会开出花如果说 turtle 是“用笔画画”那 Matplotlib 这一套就是“用公式生成坐标”。这里的灵魂是极坐标方程r a * cos(k * θ)在极坐标系中一个点的位置由到原点的距离 r 和与正 x 轴的夹角 θ 决定。这个公式的意思是当角度 θ 从 0 扫到 2π也就是 360 度时r 会随 θ 的余弦值变化。r 大点离中心远r 小点离中心近r 为负点会跳到相反方向。合起来轨迹就走出一条对称、多瓣的花形。为什么 k 决定花瓣数量当 k 是奇数时花瓣数是 k当 k 是偶数时花瓣数是 2kk 越小花瓣越宽k 越大花瓣越窄越密集。这个规律我记得不牢但实际画两次就记住了取 k5得到 5 片花瓣取 k8得到 16 片花瓣。参数 a 只控制大小不影响花瓣数量。可以先不管严谨推导把它当成一个“调参出花”的公式来玩重点感受 a、k、θ 这三个变量对最终图形的贡献。3.2 从公式到图像的完整代码用 numpy 生成数据用 Matplotlib 画图核心代码只有几行import numpy as np import matplotlib.pyplot as plt theta np.linspace(0, 2 * np.pi, 1000) r 6 * np.cos(5 * theta) # 极坐标转换到直角坐标 x r * np.cos(theta) y r * np.sin(theta) plt.figure(figsize(6, 6)) plt.plot(x, y, colorcrimson, linewidth2) plt.axis(equal) plt.axis(off) plt.show()运行后你会看到一朵五瓣玫瑰。换k8立刻变成 16 瓣换a3整体缩小换颜色colordarkorange风格立刻不一样。这个版本和 turtle 最大的不同是坐标完全由数学公式生成不会有手绘的抖动感线条非常流畅也更方便做动画和批量生成。3.3 “python画图横坐标太密集”是怎么来的怎么解决我猜不少人在这个题目上搜过“matplotlib 画图横坐标太密集”。看到这个热搜词我特别有共鸣——我也犯过这个错误。原因很简单如果你直接plt.plot(r)而 r 是 1000 个点组成的数组Matplotlib 就会拿索引0到999当横坐标自动生成几十个刻度标签挤成一团。解决办法有三个层次不要把一维数组当成单条曲线直接 plot。正确做法是同时计算x和y再用plt.plot(x, y)。用plt.axis(equal)保证横纵轴比例一致。否则长宽比例失调花瓣会被拉伸成扁平形状视觉上特别“假”。隐藏刻度plt.xticks([])、plt.yticks([])或者干脆plt.axis(off)。对于纯装饰性图案坐标刻度没有意义只会干扰观感。如果用了极坐标投影ax plt.subplot(111, projectionpolar) ax.plot(theta, r, colorcrimson, linewidth2) ax.set_xticklabels([]) ax.set_yticklabels([]) ax.grid(False)极坐标下同样会遇到刻度标签和网格线干扰的问题set_xticklabels([])和set_yticklabels([])就是用来清掉它们的。记住绘图的目的如果是“美”就去掉所有非必要元素。3.4 用多条曲线叠加出玫瑰层次单条玫瑰线是扁平线条想要丰富层次可以叠加多条相位不同、颜色深度不同的曲线。比如fig, ax plt.subplots(figsize(6, 6)) for i, k in enumerate([5, 7, 9]): r 6 * np.cos(k * theta 0.3 * i) x r * np.cos(theta) y r * np.sin(theta) ax.plot(x, y, linewidth2, alpha0.6) ax.axis(equal) ax.axis(off) plt.show()这里的0.3 * i是初始相位偏移会让每层旋转一个角度叠加后产生更复杂的视觉结构。alpha0.6是透明度透明叠起来才有“水彩重叠”的质感。你也可以用cmap色带按角度着色但新手阶段先玩明白相位和透明度就足够了。4. 进阶玩法让玫瑰动起来、炫起来、组合起来4.1 让玫瑰“生长”起来Matplotlib 动画静态图看久了会腻动画版玫瑰很适合做贺卡或者动态桌面。原理非常简单把画布和线条对象创建好然后用FuncAnimation不断更新线条的数据范围让它从起点一直画到当前帧。import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation theta np.linspace(0, 2 * np.pi, 1000) r 6 * np.cos(5 * theta) x r * np.cos(theta) y r * np.sin(theta) fig, ax plt.subplots(figsize(6, 6)) ax.axis(equal) ax.axis(off) line, ax.plot([], [], colorcrimson, linewidth2) def update(frame): n int((frame 1) * len(theta) / 100) line.set_data(x[:n], y[:n]) return line, ani FuncAnimation(fig, update, frames100, interval40, blitTrue) ani.save(rose.gif, writerpillow)保存 GIF 的时候需要 Pillow 库先pip install pillow。没有安装也能在窗口里实时预览plt.show()会打开一个动态刷新窗口。这里的性能关键点是不要每一帧重新设置坐标系只在初始时设置一次。set_data只更新坐标点成本极低如果每帧ax.plot()一次几百帧画下来会卡到怀疑人生。4.2 颜色渐变与多层叠加想让玫瑰颜色从中心到边缘渐变可以按角度给线段分段上色。一个省力的做法是画多条半径不同的玫瑰线逐层缩小透明度递增。fig, ax plt.subplots(figsize(6, 6)) for j, a in enumerate(np.linspace(6, 2, 5)): r a * np.cos(5 * theta) x r * np.cos(theta) y r * np.sin(theta) ax.plot(x, y, colorplt.cm.Reds(0.3 0.6 * j / 5), linewidth2) ax.axis(equal) ax.axis(off) plt.show()plt.cm.Reds(数值)返回一个颜色数值在 0 到 1 之间越小越浅越大越深。这样由外到内颜色逐渐加深会有一种“光影在花瓣上流动”的感觉。多试几组 colormapGreens、Oranges、Purples配色风格完全不同。4.3 玫瑰爱心文字做出适合发朋友圈的组合图把玫瑰线、爱心曲线放在同一个坐标系里再配上祝福文字就能做出一张完整小海报。爱心曲线是经典的参数方程t np.linspace(0, 2 * np.pi, 1000) heart_x 16 * np.sin(t) ** 3 heart_y 13 * np.cos(t) - 5 * np.cos(2 * t) - 2 * np.cos(3 * t) - np.cos(4 * t)组合时最需要注意的点是坐标范围。玫瑰线半径 6爱心坐标横向跨度约 30直接放一起会比例失衡。我的做法是把玫瑰线缩放并平移到爱心上方或者把爱心缩小成背景。import matplotlib.pyplot as plt import numpy as np plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False theta np.linspace(0, 2 * np.pi, 1000) r 3 * np.cos(5 * theta) rose_x r * np.cos(theta) rose_y r * np.sin(theta) 6 heart_x 16 * np.sin(theta) ** 3 heart_y 13 * np.cos(theta) - 5 * np.cos(2 * theta) - 2 * np.cos(3 * theta) - np.cos(4 * theta) plt.figure(figsize(6, 8)) plt.plot(rose_x * 0.8, rose_y * 0.8, colorcrimson, linewidth2) plt.plot(heart_x * 0.4, heart_y * 0.4 - 6, colorhotpink, linewidth2, alpha0.6) plt.text(0, -2, Happy Day, hacenter, fontsize18) plt.axis(equal) plt.axis(off) plt.show()这里中文显示要提前设置字体不然plt.text里的中文会变成方块。axes.unicode_minus也要设为 False避免负号显示异常。输出透明背景 PNG 也很简单保存时指定transparentTrueplt.savefig(rose_card.png, dpi150, transparentTrue)透明底的好处是你可以把图案直接贴到任何颜色的背景上不需要抠图。5. 运行环境与高频报错排查从安装到出图的最后一公里5.1 装好 Python 与三方库的最快路径如果你是从零开始建议直接到 Python 官网下载安装包。安装时注意勾选“Add Python to PATH”这一步不勾的话命令行里会找不到python命令。装完之后打开终端验证python --version能输出版本号就说明环境没问题。然后安装绘图需要的第三方库pip install numpy matplotlib pillow这里有个经验numpy和matplotlib不要混装太多版本。我遇到过用户环境里同时存在多个 Python 和多个 pip导致pip install的库装到了系统 Python而脚本用另一个 Python 解释器运行结果ModuleNotFoundError。排查方法很简单在脚本里先打印一下当前解释器路径import sys print(sys.executable)如果输出的路径和你安装库时用的pip不是同一个那就说明环境混了。更规范的解法是建一个虚拟环境python -m venv venvWindows 下激活是venv\Scripts\activatemacOS/Linux 下是source venv/bin/activate。在虚拟环境里安装库再运行脚本90% 的环境问题都能绕开。5.2 三个高频报错的排查链路报错并不可怕可怕的是不看报错就乱试。我梳理了这次画玫瑰过程中最容易遇到的三个问题。问题一ModuleNotFoundError: No module named matplotlib排查链路先确认你运行脚本的解释器是不是安装库时的解释器。最快捷的方法是在终端输入pip list看看里面有没有 matplotlib。如果没有直接pip install matplotlib。如果安装成功但脚本还是报错检查文件名。有没有一个叫做matplotlib.py或turtle.py的脚本挡在导入路径前面Python 导入同名文件时会优先导入当前目录下的文件这种情况隐蔽又常见。问题二plt.show()后窗口不显示图片原因一般是缺少 Tkinter 图形界面依赖。matplotlib 默认后端是TkAgg需要 Tkinter 支持。Linux 系统尤其容易缺这个组件安装对应包即可。如果确实不想装图形界面也可以直接把图保存成文件plt.savefig(rose.png, dpi150)保存文件比显示窗口省心很多也方便批量出图。问题三turtle 运行时窗口一闪而过这个最经典。如果你在脚本里写了turtle.done()或者turtle.mainloop()窗口会保持打开状态如果没写脚本执行完窗口立即关闭。解决办法就是在代码最后加上turtle.done()另外如果在 IDE 里运行 turtle某些 IDE 会接管事件循环可能出现RuntimeError: main thread is not in main loop。遇到这种情况优先在终端里直接运行脚本而不是在 IDE 里点运行按钮。5.3 画得太慢或卡死怎么办turtle 画复杂玫瑰最影响体验的就是慢。解决办法已经在前面提过turtle.tracer(0, 0)关闭逐帧刷新画完再turtle.update()。如果你要画几十层花瓣这一步做完运行时间能从几分钟降到几秒。matplotlib 这边性能瓶颈主要在数据点数量和动画帧数。玫瑰线的采样点从 500 加到 2000 画质区别明显但加到 20000 就开始浪费内存。我一般固定 1000 左右足够流畅。动画保存 GIF 时帧数别贪多100 帧完全够用每帧间隔 40 毫秒视觉上非常顺滑。还有个容易被忽略的点plt.show()默认会阻塞脚本直到你手动关闭窗口。如果你做的是批量生成图片建议用plt.close(fig)或者直接只调用savefig避免程序卡在第一步。6. 再往里走一步把趣味练习沉淀成自己的小作品6.1 把参数封装成函数随手生成不同玫瑰一旦理清了玫瑰线公式就可以把所有变量收敛成函数参数做成一个“玫瑰生成器”。我个人非常推荐这个习惯别满足于一段能跑通的脚本要把它改造成可以复用的工具。def draw_rose(k5, a6, phase0, colorcrimson, alpha1.0): theta np.linspace(0, 2 * np.pi, 1000) r a * np.cos(k * theta phase) x r * np.cos(theta) y r * np.sin(theta) plt.plot(x, y, colorcolor, linewidth2, alphaalpha)以后想生成三瓣、五瓣、七瓣或者不同颜色风格的玫瑰只需要调用函数。这种“把核心逻辑参数化”的思路比去背任何绘图 API 都管用。你再往上封装还可以加入save_path参数、自动隐藏轴线、批量生成一堆玫瑰。6.2 用“点阵”思路画出不一样的玫瑰如果你想继续往深处走可以试试不用线条而是用像素点阵来画玫瑰生成一张二维网格判断每个像素点到玫瑰线的距离距离足够近就把该像素点亮。这样得到的玫瑰是离散的点阵效果很有数字感。实现上会用到 numpy 的广播特性和条件筛选属于把“公式画图”和“图像处理”结合起来的练习。grid np.linspace(-8, 8, 800) X, Y np.meshgrid(grid, grid) theta np.arctan2(Y, X) r np.sqrt(X**2 Y**2) R 6 * np.cos(5 * theta) distance np.abs(r - R) mask distance 0.15mask是一个二维布尔数组True 的点组成玫瑰形状。你可以把它当成花草茶泡袋上的图案生成器来理解一个网格里面只有满足条件的点才被保留。这种练习的好处是你已经从“调用绘图库”上升到了“理解像素和坐标转换”后面再去碰图像处理、数据可视化思维会顺很多。6.3 一份真实的个人体会我最初学 Python 的时候对画图一直停留在“跟着抄、能跑就行”的阶段。直到捡起“绘制玫瑰花图案”这个题目被迫去研究坐标系、极坐标、动画帧更新再到把不同方案组合成一张完整的贺卡才真正有“Python 不只是写脚本而是能表达想法”的感觉。如果你也在用这个题目练手我的建议是别只满足于复制别人的成品试着改改参数、换换颜色、加一段文字把一个 5 行代码的玩具慢慢变成属于自己的作品。这个过程里踩过的每一个报错最后都会成为你对图形和坐标轴最直观的理解。
阅读完成 · 觉得有帮助?
咨询建站