1. 为什么用turtle画三角函数图像是个好主意
1.1 从考研数学的痛点说起
备考数学一或数学二的人都有一个共同体会:三角函数和反三角函数的图像,光靠死记硬背特别容易搞混。比如 y = arcsin(x) 和 y = arccos(x) 的图像到底怎么交叉、定义域为什么分别是 [-1,1]、值域为什么一个是 [-π/2, π/2] 另一个是 [0, π],这些东西在考场上紧张起来真的会记岔。我当时复习到反三角函数那一章,看着课本上那些静态的图,总觉得少了点什么——直到我想起来 Python 里有个 turtle 库,可以一笔一笔地把这些曲线画出来。
turtle 是 Python 标准库中一个非常直观的绘图模块,最初是为教育场景设计的。它的工作方式就像你拿着一支笔在纸上画线:你告诉笔往哪个方向走多少距离、什么时候抬笔、什么时候落笔,它就在画布上留下轨迹。用它来画函数图像,最大的好处是你能亲眼看到每一个点是怎么被计算出来、怎么被连成线的。这个过程对理解函数的定义域、值域、单调性、对称性都有极大的帮助。
这篇文章适合谁看?如果你正在备考数学类研究生入学考试,或者你是个正在学 Python 的初学者想找个有趣的小项目练手,又或者你是个教数学的老师想找个可视化工具辅助教学,那这篇内容应该能给你一些可以直接拿来用的东西。我会从最基础的环境准备讲起,一步步把正弦、余弦、正切、反正弦、反余弦、反正切六个函数的图像画出来,中间会涉及坐标系变换、采样点选取、渐近线处理这些实操细节。
1.2 turtle绘图相比matplotlib的独特价值
有人可能会问:画函数图像不是应该用 matplotlib 吗?一行plt.plot(x, y)就搞定了,为什么要用 turtle 这么“原始”的工具?
这个问题问得好。matplotlib 确实是数据可视化的标准工具,效率高、功能强。但 turtle 有一个 matplotlib 替代不了的优势:过程可见。当你用 turtle 画图时,你能看到画笔从原点出发,一个点一个点地移动,逐渐勾勒出曲线的形状。这种“过程感”对于理解函数的连续变化特别有帮助。尤其是画反三角函数时,你会直观地感受到当 x 趋近于 1 时 arcsin(x) 的值是如何趋近于 π/2 的——这种感受是静态图像给不了的。
另外,turtle 是 Python 标准库自带的,不需要额外安装任何东西。对于刚装好 Python 环境的新手来说,打开 IDLE 就能直接写代码运行,零配置成本。这一点在考研复习的碎片化时间里特别重要——你不需要花半小时折腾环境,打开就能画。
当然,turtle 也有它的局限:绘图速度慢、不适合大数据量渲染、没有内置的坐标轴系统。但这些局限在本文的场景下都可以通过合理的代码设计来规避。我会在后面的章节里详细说明怎么处理这些问题。
2. 环境准备与坐标系设计
2.1 确认Python环境和turtle可用性
在开始写代码之前,先确认你的 Python 环境能正常运行 turtle。打开终端或命令行,输入:
python -c "import turtle; print('turtle is ready')"如果输出了turtle is ready,说明一切就绪。如果报错说找不到模块,大概率是你的 Python 安装不完整,建议去官网重新下载一个完整版安装包。
注意:在某些 Linux 发行版上,turtle 依赖 tkinter 库。如果导入 turtle 时报
ModuleNotFoundError: No module named 'tkinter',需要先安装 tkinter。在 Ubuntu/Debian 上可以运行sudo apt-get install python3-tk。
我个人的习惯是在项目文件夹里建一个单独的.py文件来写代码,而不是在交互式环境里一行行敲。因为画函数图像涉及多个函数定义和参数调整,放在文件里方便反复修改和运行。
2.2 建立数学坐标系到屏幕坐标系的映射
turtle 默认的坐标系原点在画布正中央,x 轴向右为正,y 轴向上为正,单位是像素。这和数学坐标系的方向一致,但单位不同——数学上我们关心的是弧度值,而 turtle 关心的是像素距离。所以需要一个映射关系。
假设我希望在屏幕上展示 x 从 -2π 到 2π 的范围,y 从 -2 到 2 的范围。画布大小设为 800×600 像素。那么:
- x 方向的缩放比例:800 / (4π) ≈ 800 / 12.566 ≈ 63.66 像素/单位
- y 方向的缩放比例:600 / 4 = 150 像素/单位
但如果 x 和 y 用不同的缩放比例,画出来的圆就不是圆了,函数图像的斜率也会失真。所以更好的做法是使用统一的缩放比例,取两者中较小的那个:
scale = min(800 / (4 * math.pi), 600 / 4)这样算下来 scale ≈ 63.66,y 方向实际只用了 63.66 × 4 ≈ 254.6 像素,画布上下会留出一些空白,但图像比例是准确的。
我在实际画图时通常会把缩放比例设成一个可调参数,方便根据不同的函数调整视野范围。比如画 tan(x) 时 y 方向需要更大的范围,就可以单独调整。
2.3 绘制坐标轴的实操步骤
坐标轴是函数图像的“骨架”,没有它你根本分不清哪里是原点、哪里是 π/2。用 turtle 画坐标轴的基本思路是:
- 抬笔移动到 x 轴左端,落笔,画到右端,加箭头
- 抬笔移动到 y 轴下端,落笔,画到上端,加箭头
- 在轴上标注刻度和数值
这里有个细节:turtle 的write()方法可以在画布上写字,但字体大小和位置需要手动调整。我一般用turtle.write(text, font=("Arial", 10, "normal"))来标注刻度值。
import turtle import math screen = turtle.Screen() screen.setup(900, 700) screen.title("三角函数图像") pen = turtle.Turtle() pen.speed(0) pen.hideturtle() def draw_axes(scale, x_range, y_range): """绘制坐标轴""" # x轴 pen.penup() pen.goto(-x_range * scale, 0) pen.pendown() pen.goto(x_range * scale, 0) # x轴箭头 pen.stamp() # 简化处理,实际可以用多边形画箭头 # y轴 pen.penup() pen.goto(0, -y_range * scale) pen.pendown() pen.goto(0, y_range * scale)这段代码是骨架,实际使用时还需要加上箭头、刻度线和数值标注。刻度线的画法是:在轴上每个整数位置画一条短垂线,然后用write()标注数值。
实操心得:turtle 的
write()默认对齐方式是居中对齐,标注刻度时最好用align="center",标注轴标签时用align="left"或align="right",这样文字不会和轴重叠。
3. 三角函数图像的绘制细节
3.1 正弦和余弦函数的采样与绘制
画 y = sin(x) 的核心逻辑很简单:在 x 的取值范围内取一系列采样点,计算对应的 y 值,然后把这些点连起来。但采样点的数量和分布有讲究。
如果采样点太少(比如只取 10 个点),曲线会变成折线,看起来很不光滑。如果采样点太多(比如取 10000 个点),turtle 画起来会非常慢,因为每个点都要执行一次移动操作。我的经验是:对于 sin 和 cos 这种平滑函数,取 200 到 500 个采样点就足够了。在 x 范围是 [-2π, 2π] 的情况下,500 个点意味着相邻两点的 x 间隔约为 0.025 弧度,画出来的曲线肉眼看起来已经很平滑了。
def draw_function(func, x_min, x_max, scale, num_points=500, color="blue"): """通用函数图像绘制""" pen.pencolor(color) pen.penup() dx = (x_max - x_min) / num_points first_point = True for i in range(num_points + 1): x = x_min + i * dx try: y = func(x) except (ValueError, ZeroDivisionError): first_point = True continue # 检查y值是否在合理范围内 if abs(y) > 10: first_point = True continue screen_x = x * scale screen_y = y * scale if first_point: pen.goto(screen_x, screen_y) pen.pendown() first_point = False else: pen.goto(screen_x, screen_y) pen.penup()这段代码里有两个关键处理:一是用try-except捕获异常(比如 tan 在 π/2 处会除零),二是检查 y 值是否超出合理范围(防止渐近线附近的点把图像拉得乱七八糟)。这两个处理在画 tan 和反三角函数时特别重要。
对于 cos(x),只需要把func换成math.cos就行。但要注意,cos 的图像和 sin 形状一样,只是相位差了 π/2。如果你想在一张图上同时画 sin 和 cos 来对比,可以用不同的颜色区分:
draw_function(math.sin, -2*math.pi, 2*math.pi, scale, color="blue") draw_function(math.cos, -2*math.pi, 2*math.pi, scale, color="red")注意事项:turtle 的
pencolor()设置后会对后续所有绘制生效,所以在画不同函数之前记得切换颜色。另外,如果两条曲线有交叉,后画的会覆盖先画的,视觉上可能造成混淆。可以考虑用不同的线宽或虚线样式来区分。
3.2 正切函数与渐近线的处理技巧
tan(x) 是三个基本三角函数里最难画的一个,因为它在 ±π/2、±3π/2 等位置有垂直渐近线。如果你直接采样然后连线,在渐近线附近会出现一条几乎垂直的线,把图像搞得很难看。
处理这个问题的标准做法是:当函数值超过某个阈值时,断开当前线段,抬笔移动到下一个有效点再重新落笔。这个阈值取多少合适?我一般取 y 轴显示范围的上限,比如 y 范围是 [-4, 4],那阈值就设为 4。当 |tan(x)| > 4 时,认为这个点“跑出画面了”,不画它,并且断开线段。
def draw_tan(scale, x_min, x_max, y_limit=4): """专门处理tan函数的绘制""" pen.pencolor("green") pen.penup() num_points = 1000 # tan变化快,需要更多采样点 dx = (x_max - x_min) / num_points first_point = True for i in range(num_points + 1): x = x_min + i * dx y = math.tan(x) if abs(y) > y_limit: first_point = True continue screen_x = x * scale screen_y = y * scale if first_point: pen.goto(screen_x, screen_y) pen.pendown() first_point = False else: pen.goto(screen_x, screen_y) pen.penup()这里采样点数量增加到了 1000,因为 tan 在渐近线附近变化极快,采样点太少会导致曲线形状失真。另外,y_limit参数控制渐近线的“截断高度”,你可以根据画布的 y 轴范围来调整。
实操心得:画 tan 的时候,渐近线附近的那几个点即使被过滤掉了,曲线在接近渐近线时仍然会显得很陡。如果你想让图像更“干净”,可以在渐近线位置画一条虚线表示渐近线本身。turtle 画虚线的方法是在
pendown()和penup()之间交替,每次移动一小段距离。
3.3 图像标注与刻度系统的完善
一张合格的函数图像不能只有曲线,还需要有清晰的标注。我通常会在图上标注以下几样东西:
- 函数表达式(如 y = sin(x)),放在曲线附近
- 关键点的坐标(如 sin 的零点、最大值点)
- x 轴上的特殊刻度:π/2、π、3π/2、2π 等
标注 π 相关的刻度有个小技巧:turtle 的write()不支持直接写希腊字母 π,但可以用 Unicode 字符\u03c0来表示。在 Python 字符串里写成"\u03c0"就能显示 π 符号。
def label_pi_ticks(scale, x_max_pi=2): """在x轴上标注π相关的刻度""" pen.pencolor("black") for i in range(1, x_max_pi * 2 + 1): x = i * math.pi / 2 screen_x = x * scale # 画刻度线 pen.penup() pen.goto(screen_x, -5) pen.pendown() pen.goto(screen_x, 5) # 标注文字 pen.penup() pen.goto(screen_x, -20) if i % 2 == 0: label = f"{i//2}\u03c0" if i > 2 else "\u03c0" else: label = f"{i}\u03c0/2" pen.write(label, align="center", font=("Arial", 9, "normal"))这段代码会在 x 轴的正半轴上标注 π/2、π、3π/2、2π 等位置。负半轴同理,只需要把 x 取负值即可。
4. 反三角函数图像的绘制要点
4.1 反三角函数的定义域限制与处理
反三角函数和基本三角函数最大的区别在于:它们的定义域是受限的。arcsin(x) 和 arccos(x) 只在 [-1, 1] 上有定义,arctan(x) 虽然在整个实数轴上都有定义,但值域被限制在 (-π/2, π/2) 内。
这意味着在写绘图代码时,不能像画 sin 那样简单地在 [-2π, 2π] 上采样。对于 arcsin 和 arccos,采样范围必须严格限制在 [-1, 1] 内。如果超出这个范围,Python 的math.asin()会抛出ValueError。
def draw_arcsin(scale): """绘制arcsin(x)图像""" pen.pencolor("purple") pen.penup() num_points = 300 x_min, x_max = -1, 1 dx = (x_max - x_min) / num_points first_point = True for i in range(num_points + 1): x = x_min + i * dx y = math.asin(x) screen_x = x * scale screen_y = y * scale if first_point: pen.goto(screen_x, screen_y) pen.pendown() first_point = False else: pen.goto(screen_x, screen_y) pen.penup()注意这里的 scale 和画 sin 时用的 scale 不一样。因为 arcsin 的 x 范围只有 [-1, 1],如果还用原来的 scale(约 63.66),图像在 x 方向只占 127 像素,太窄了。所以画反三角函数时,我通常会把 scale 调大,比如设为 150 或 200,让图像在画布上占据合适的比例。
4.2 值域可视化:用颜色区分不同分支
反三角函数的值域是理解它们的关键。arcsin 的值域是 [-π/2, π/2],arccos 的值域是 [0, π],arctan 的值域是 (-π/2, π/2)。这些值域范围在图像上表现为曲线的“高度范围”。
一个很好的可视化方法是:用不同颜色标注值域的上下界。比如画 arcsin 时,用一条水平虚线标出 y = π/2 和 y = -π/2 的位置,然后用另一种颜色画出曲线本身。这样一眼就能看出曲线被“夹”在这两条线之间。
def draw_range_lines(scale, y_top, y_bottom, x_range=1.5): """画值域边界虚线""" pen.pencolor("gray") pen.pensize(1) for y in [y_top, y_bottom]: pen.penup() pen.goto(-x_range * scale, y * scale) pen.pendown() # 画虚线 for _ in range(20): pen.forward(10) pen.penup() pen.forward(5) pen.pendown() pen.penup()这段代码画出来的虚线可以帮助你直观地看到值域的边界。对于 arccos,上界是 π,下界是 0;对于 arctan,上界是 π/2,下界是 -π/2。
4.3 反三角函数图像的对称性展示
反三角函数有一个非常重要的性质:原函数和反函数的图像关于直线 y = x 对称。这个性质在考研中经常考到,但很多人只是记住结论,没有真正“看到”过。
用 turtle 可以很直观地展示这个对称性:在同一张图上画出 sin(x) 在 [-π/2, π/2] 上的图像,再画出 arcsin(x) 的图像,然后画一条 y = x 的虚线。你会看到两条曲线关于这条虚线完美对称。
def draw_symmetry_line(scale, length=2): """画y=x对称轴""" pen.pencolor("orange") pen.pensize(1) pen.penup() pen.goto(-length * scale, -length * scale) pen.pendown() pen.goto(length * scale, length * scale) pen.penup()这个对称性展示对于理解反函数的本质特别有帮助。我在复习的时候,每次搞混 arcsin 和 arccos 的图像,就想想它们和原函数的对称关系,很快就能回忆起来。
注意事项:画对称性展示图时,sin 和 arcsin 必须使用相同的 scale,否则对称关系会失真。另外,sin 的图像只需要画 [-π/2, π/2] 这一段,因为 arcsin 的值域就是这个范围。
5. 常见问题与排查技巧实录
5.1 图像画出来是折线怎么办
这是新手最常遇到的问题。原因通常有两个:采样点太少,或者speed()设置得太快导致视觉上看起来是折线。
如果是采样点太少,解决办法很简单:把num_points从默认的 100 增加到 500 甚至 1000。但要注意,采样点增加后绘图时间会变长。我的经验是:对于 sin 和 cos,500 个点足够;对于 tan,需要 1000 个点;对于反三角函数,300 个点就够(因为定义域窄)。
如果是speed()的问题,把速度设为 0(最快)或 10(较快)就行。但有时候速度太快反而看不清绘制过程,如果你是想用 turtle 来教学演示,建议把速度设为 3 到 5,让学生能看到画笔的移动轨迹。
5.2 渐近线附近的异常连线
画 tan 和 arctan 时,如果不做特殊处理,渐近线附近会出现一条横跨整个画布的直线。这是因为 turtle 在抬笔和落笔之间的状态没有正确切换。
解决办法就是我前面提到的:当函数值超出阈值时,设置first_point = True,这样下一个有效点会重新执行penup()和pendown()的逻辑。这个技巧在画任何有间断点的函数时都适用。
另外,arctan 虽然在整个实数轴上有定义,但它的值域被限制在 (-π/2, π/2) 内,所以画 arctan 时不需要断开线段,只需要把 x 范围取大一点(比如 [-10, 10]),就能看到曲线逐渐趋近于水平渐近线。
5.3 坐标系比例失调的修正方法
如果你发现画出来的圆变成了椭圆,或者函数图像的斜率看起来不对,那多半是 x 和 y 方向的缩放比例不一致。
修正方法很简单:用同一个 scale 值来映射 x 和 y。具体做法是:
scale_x = width / (x_max - x_min) scale_y = height / (y_max - y_min) scale = min(scale_x, scale_y) # 取较小值保证图像完整显示这样 x 和 y 用同一个 scale,图像比例就是准确的。代价是画布上可能会有一些空白区域,但这是值得的。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 曲线是折线 | 采样点太少 | 增加 num_points 到 500+ |
| 渐近线处有横线 | 未处理间断点 | 超出阈值时设 first_point=True |
| 图像比例失真 | x/y缩放比例不同 | 使用统一的 scale 值 |
| 反三角函数报错 | 采样超出定义域 | 限制 x 范围在 [-1,1] |
| 绘图速度太慢 | 采样点过多或速度设置低 | 减少采样点或设 speed(0) |
| π符号显示为方框 | 字体不支持 | 换用支持Unicode的字体 |
| 曲线被画布边缘截断 | 缩放比例过大 | 减小 scale 或扩大画布 |
实操心得:我在调试绘图代码时,习惯先用少量采样点(比如 20 个)快速跑一遍,确认坐标系和基本形状没问题后,再增加到最终采样点数量。这样能节省大量等待时间。
6. 完整代码框架与扩展思路
6.1 一个可直接运行的完整示例
把前面所有的片段整合起来,下面是一个可以直接复制运行的完整代码框架。它会在同一张画布上画出 sin、cos、tan 三条曲线,并标注坐标轴和关键刻度。
import turtle import math # 初始化 screen = turtle.Screen() screen.setup(1000, 700) screen.title("三角函数图像 - 考研复习辅助") screen.bgcolor("white") pen = turtle.Turtle() pen.speed(0) pen.hideturtle() pen.pensize(2) # 参数设置 SCALE = 60 X_RANGE = 2 * math.pi Y_RANGE = 2 def setup_axes(): """绘制坐标轴""" pen.pencolor("black") pen.pensize(1) # x轴 pen.penup() pen.goto(-X_RANGE * SCALE - 20, 0) pen.pendown() pen.goto(X_RANGE * SCALE + 20, 0) # y轴 pen.penup() pen.goto(0, -Y_RANGE * SCALE - 20) pen.pendown() pen.goto(0, Y_RANGE * SCALE + 20) # 标注原点 pen.penup() pen.goto(-15, -15) pen.write("O", font=("Arial", 12, "normal")) def draw_func(func, x_min, x_max, color, num=500, y_limit=10): """通用函数绘制""" pen.pencolor(color) pen.pensize(2) pen.penup() dx = (x_max - x_min) / num first = True for i in range(num + 1): x = x_min + i * dx try: y = func(x) except (ValueError, ZeroDivisionError): first = True continue if abs(y) > y_limit: first = True continue sx, sy = x * SCALE, y * SCALE if first: pen.goto(sx, sy) pen.pendown() first = False else: pen.goto(sx, sy) pen.penup() # 绘制 setup_axes() draw_func(math.sin, -X_RANGE, X_RANGE, "blue") draw_func(math.cos, -X_RANGE, X_RANGE, "red") draw_func(math.tan, -X_RANGE, X_RANGE, "green", num=1000, y_limit=Y_RANGE) # 图例 pen.penup() pen.goto(X_RANGE * SCALE - 100, Y_RANGE * SCALE - 30) pen.pencolor("blue") pen.write("y=sin(x)", font=("Arial", 10, "normal")) pen.goto(X_RANGE * SCALE - 100, Y_RANGE * SCALE - 50) pen.pencolor("red") pen.write("y=cos(x)", font=("Arial", 10, "normal")) pen.goto(X_RANGE * SCALE - 100, Y_RANGE * SCALE - 70) pen.pencolor("green") pen.write("y=tan(x)", font=("Arial", 10, "normal")) screen.exitonclick()运行这段代码,你会看到一张包含三条三角函数曲线的图像。你可以修改SCALE来调整缩放,修改X_RANGE来调整视野范围。
6.2 扩展到反三角函数的一键切换方案
如果你想画反三角函数,只需要把draw_func的调用换成对应的函数,并调整 x 范围:
# 画arcsin draw_func(math.asin, -1, 1, "purple", num=300) # 画arccos draw_func(math.acos, -1, 1, "orange", num=300) # 画arctan draw_func(math.atan, -10, 10, "brown", num=500)但要注意,画反三角函数时 SCALE 需要调大,因为定义域窄。我通常会把 SCALE 设为 150 到 200,这样 [-1, 1] 的范围在画布上才有足够的宽度。
6.3 后续可以继续深挖的方向
这个项目本身已经能覆盖考研数学中三角函数图像的基本需求,但如果你想继续深挖,还有几个方向可以扩展:
第一个方向是动态绘制。用ontimer()方法让曲线一个点一个点地出现,配合数值显示当前点的坐标,做成一个动态演示工具。这对理解函数的连续性特别有帮助。
第二个方向是参数可调。给代码加上命令行参数或简单的 GUI 输入框,让用户可以自己输入函数的系数(比如 y = A·sin(ωx + φ)),实时看到图像变化。这对理解振幅、周期、相位这些概念很有用。
第三个方向是多图对比。把原函数和反函数画在同一张图上,用不同颜色区分,再加上 y = x 对称轴,直观展示反函数的对称性质。
第四个方向是导出高清图片。turtle 本身不支持直接导出高分辨率图片,但可以通过screen.getcanvas().postscript()方法导出 PostScript 文件,再用其他工具转成 PNG。这样画出来的图可以直接放进复习笔记里。
最后分享一个小技巧:如果你觉得 turtle 的绘图窗口太小,可以在
screen.setup()里把尺寸设大一点,比如 1200×800。但要注意,窗口太大时 turtle 的绘制速度会明显变慢,因为每个像素的渲染开销增加了。我的建议是 1000×700 左右比较平衡,既能看清细节,速度也可以接受。