如果你正在用 Python 海龟绘图刷题,到“画三角形”这一关,你多半会觉得简单:forward()画边,left()/right()转弯,三条边转三次,完事。可到了“钝角三角形”这道题,很多初学者会突然卡住——不是不会写代码,而是对海龟转向里的“角度”理解偏了。
有人想画一个 120° 的钝角三角形,随手写turtle.left(120),结果画出来的图形根本不是自己想要的;还有人发现,自己明明指定了三条边的长度,三角形也确实画出来了,可最大角怎么看都是锐角。
问题出在哪?其实不在于 Python 语法,而在于一个容易被忽略的细节:海龟转弯时真正需要计算的是三角形内角的补角,也就是外角,而不是内角本身。这道题的真正价值,不是让你背下某个固定代码,而是让你把“内角、外角、转向角”这三者的关系彻底搞清楚。这个关系一旦打通,钝角三角形、锐角三角形、直角三角形、等腰三角形、等边三角形,就都只是同一套逻辑的不同参数罢了。
本文会从题目拆解开始,讲清楚海龟绘图中的角度本质,再给出三种从易到难的实现方式,并配上完整可运行的代码、输出效果和排查思路。建议先收藏再阅读。
1. 钝角三角形这道题的真正难点
先看题目本身:用 Python 海龟绘图画一个钝角三角形。
钝角三角形的定义很明确:三角形中有一个内角大于 90°。比如 120°、100°、95°,只要大于 90° 且小于 180° 就可以。也就是说,只要有一个角是钝角,其余两个角必然都是锐角,且它们的和小于 90°。这个几何常识看起来很基础,但一旦落到海龟绘图中,就会出现一个“理解断层”。
很多初学者会这样想:既然要画一个带 120° 角的钝角三角形,那我就在海龟走到某个顶点时,让它向左转 120°,这样顶点处不就形成 120° 了吗?
听起来完全合理,但实际执行会出问题。原因是海龟每次转向时,它改变的并不是三角形内部的角度,而是海龟自身朝向与原来行进方向的夹角。在闭合图形的绘制中,这个夹角恰好是当前顶点处内角的补角。简单说:
- 三角形某个内角为 120°;
- 海龟要让下一条边和上一条边之间形成 120° 夹角,它实际转动的角度是 180° - 120° = 60°;
- 如果直接
left(120),画出来的内角反而是 60°,图形变成锐角三角形或无法闭合。
换句话说,这道题表面上是“画图题”,实际是在考“补角与外角关系”。只有先理解这个关系,后面的代码才有意义。
此外,还有一个常见的认知陷阱:有人会误以为“钝角三角形看起来应该很扁”,于是凭感觉设计三条边的长度,结果画出来的是个锐角或直角三角形。为了避免这种误差,建议先用三角形的角度约束来确定顶点坐标,或者用“画一条底边 + 从上方连接一个顶点”的方式,让钝角的位置清晰可控。这道题的难点不在于输入forward()和left(),而在于:你如何保证代码画出来的角度,和几何计算中算出来的角度是同一个角度。
2. 海龟绘图中的角度:内角、外角与转向角
想要把钝角三角形画对,先要把 turtle 模块中“角度”这一套概念理清楚。这里用最直白的方式解释。
2.1 内角与外角
在任意多边形内部,相邻两条边在顶点处形成的夹角叫内角。把其中一条边延长,延长线与另一条边形成的夹角叫外角。对于一条直线上的同一顶点,内角和外角互补,和为 180°。
比如等边三角形,每个内角是 60°,对应的外角就是 120°。海龟画等边三角形时,每画完一条边要转 120°——它用的就是外角。所以很多教材会说:画正多边形时,海龟每次转弯的角度等于 360° 除以边数。
到这里你会发现,画三角形也遵循同样的逻辑:
外角 = 180° - 内角正因为海龟画的是一条连续的折线,它在一个顶点转向时所旋转的角度,其实是延长上一条边后形成的那个夹角,也就是外角。如果想让三角形某个内角为 120°,那么海龟在该顶点处应转动的角度是 60°,而不是 120°。
2.2 转向方向与正负
turtle 中默认海龟方向朝屏幕右侧,也就是 0° 方向。left(90)会让海龟逆时针转了 90°,朝向屏幕正上方;right(90)则顺时针转向。对于三角形来讲,只要统一使用left()或统一使用right(),图形就可以保持一致的走向。
需要注意,转向角度并不是越大越“钝”。一个内角为 120° 的顶点,其外角反而是 60°;一个内角为 30° 的顶点,外角则是 150°。也就是说:
- 内角越大,海龟转弯越小;
- 内角越小,海龟转弯越大。
这条关系特别反直觉,它是初学者最容易搞反的地方。
2.3 闭合与角度总和
一个闭合的三角形,海龟沿轮廓走完一圈后,所有外角的总和一定是 360°。
如果我们用三个内角分别为 A、B、C 列等式:
(180 - A) + (180 - B) + (180 - C) = 540 - (A + B + C)由于 A + B + C = 180°,所以外角和为 540° - 180° = 360°。
这就是为什么你可以用任意一组满足“和为 180°”的内角组合,画出精确闭合的三角形。代码层面只要保证三段边长相加能回到起点,以及三个外角之和为 360°,图形一定能闭合。如果图形没有闭合,往往是边长或角度之间出现了不一致。
2.4 海龟坐标系的方向
理解 turtle 的角度还离不开坐标系。默认情况下,窗口中心是坐标原点 (0, 0),海龟初始方向为右,即数学上的 x 轴正方向;逆时针方向为角度增加方向。
三角形在屏幕上放的位置、朝向,都取决于你从哪个点开始画、海龟初始朝向如何。这给了绘制策略很大的自由度:你可以从底边开始画,也可以从某个顶点开始画;可以先画钝角所在的边,也可以把三个顶点都算好以后直接用goto()连线。
这些细节听起来琐碎,但它们共同决定了代码的稳定性和可读性。下面三种实现方式,就是从“固定角度法”到“坐标法”再到“带检验的代码法”的递进。
3. 准备环境:Python 与 turtle 模块
turtle 模块是 Python 标准库的一部分,不需要额外安装。只要你的电脑上有 Python 环境,就可以直接运行。
import turtle print(turtle.__file__)如果能正常输出模块路径,说明环境可用。如果还没有安装 Python,可以前往 Python 官网下载对应系统版本的安装包。安装时建议勾选“Add Python to PATH”。安装完成后,在命令行输入python --version验证环境变量是否生效。
开发工具方面,IDLE、VS Code、PyCharm 都可以。需要注意一点:在 VS Code 或 PyCharm 里运行 turtle 程序时,偶尔会出现窗口一闪而过的情况,通常是因为海龟窗口运行完就被自动关闭。解决方式是:绘制完成后调用turtle.done(),让窗口保持打开。
推荐的使用流程是:先新建.py文件,比如obtuse_triangle.py,然后在终端执行:
python obtuse_triangle.py也可以逐行在交互式环境里测试,但多行循环代码建议写成脚本,更方便观察图形效果。
另外可以做一些屏幕设置,让图形更清晰:
import turtle t = turtle.Turtle() t.speed(5) turtle.screensize(600, 600) turtle.title("Python 海龟绘图:钝角三角形")这些代码不是必需的,但能让我们的练习过程更直观。
4. 方法一:用“外角 = 180° - 内角”绘制等腰钝角三角形
先看最经典的钝角三角形:等腰三角形,顶角为 120°,两个底角为 30°。因为两腰相等,只要设置好顶角位置,底边长度自动确定,代码实现非常稳定。
4.1 设计思路
等腰三角形中,假设顶角是 120°,那么:
- 顶角外角 = 180° - 120° = 60°;
- 底角外角 = 180° - 30° = 150°。
我们选择先画一条腰,再画两条同样长度的边。如果等腰三角形的腰长为 150 像素,代码逻辑是:
- 海龟从某个点出发,沿水平方向画一条 150 像素的线段;
- 转向 150°,再画 150 像素的第二条腰;
- 转向 60°,画底边。
之所以先转 150°,是因为要让三角形的一个底角变为 30°。画完两条腰后,海龟处在顶点位置,再转 60° 画底边,既能让最后的顶角成为 120°,也能让底边自然闭合到起点。
你可能已经发现,这里的转向顺序和角度的对应关系并不直观。更稳妥的方式是:先明确三个顶点的摆放位置,再决定先画哪个角。对于等腰三角形,可以这样操作:
- 从底边左端点出发;
- 沿底边画整条底边;
- 在右端点向左转 150°,画右腰;
- 在顶点向左转 60°,画左腰,回到起点。
这种方式的好处是底边在下方,钝角顶点在上方,图形方向符合日常认知。
4.2 计算底边长度
设腰长为 s,顶角为 120°,则底边长度可由余弦定理计算:
底边² = s² + s² - 2 * s * s * cos(120°)120° 的余弦值为 -0.5,代入可得:
底边² = 2 * s² + s² = 3 * s² 底边 = s * √3如果 s = 150,则底边约等于 150 * 1.732 = 259.8。为了减少浮点误差带来的闭合偏差,可以先计算好底边长度,再把它作为变量使用。
4.3 完整代码
import turtle import math # 设置画布和海龟 screen = turtle.Screen() screen.setup(800, 600) screen.title("Python 海龟绘图:等腰钝角三角形(顶角120°)") t = turtle.Turtle() t.speed(5) t.pensize(2) t.pencolor("blue") # 等腰三角形的腰长 side = 150 # 顶角为120度,底角为30度 # 底边长度 = side * sqrt(3) base = side * math.sqrt(3) # 从底边左端点开始画,保证图形横向居中 start_x = -base / 2 start_y = -50 t.penup() t.goto(start_x, start_y) t.pendown() # 画底边 t.forward(base) # 在底边右端点,转向角是底角的外角:180 - 30 = 150 t.left(150) # 画右边腰 t.forward(side) # 此时在钝角顶点,转向角是顶角的外角:180 - 120 = 60 t.left(60) # 画左边腰,回到起点 t.forward(side) # 绘制辅助点,方便观察顶点 t.penup() t.goto(start_x, start_y) t.dot(8, "red") t.write("A", font=("Arial", 14, "bold")) t.goto(start_x + base, start_y) t.dot(8, "red") t.write("B", font=("Arial", 14, "bold")) t.goto(start_x + base / 2, start_y) t.dot(8, "green") t.write("C", font=("Arial", 14, "bold")) t.hideturtle() screen.mainloop()4.4 代码逻辑说明
第一步把海龟移动到底边左端点 A 的坐标(-base/2, -50)。这个位置让整个三角形大致位于窗口中央,不会跑出画布。
第二步画底边,长度为base,对应余弦定理的计算结果。由于底边上的两个底角各为 30°,所以在 B 点需要转 150° 才能让海龟朝向左上方,开始画右侧腰。
第三步画完腰后,海龟自动停在顶点 C。此时还要转到下一个方向:C 点内角为 120°,因此外角是 60°。转动 60° 后,海龟方向会正好指向左下方向的 A 点。
第四步画左侧腰时,长度仍然使用side = 150,所以能够恰好回到起点,形成闭合三角形。这种“夹角 + 长度”双重约束下的闭合,正是用海龟画多边形最规范的方式。
如果你运行后发现三角形没有回到起点,优先检查角度的外角关系,其次是边长是否满足余弦定理。如果只是闭合误差在几个像素以内,通常属于浮点计算误差,可以用round()或直接让边长为整数值来规避。
5. 方法二:用坐标直接画出任意钝角三角形
等腰钝角三角形虽然有对称美,但题目并没有限定“等腰”。如果希望画出一般的钝角三角形,比“边走边转”更稳定的思路是:先用数学计算确定三个顶点的平面坐标,再用goto()连线。这种方式把几何问题从“计算角度 + 画弧线”变成了纯粹的坐标计算,对角度感知较弱的新手也更友好。
5.1 设计思路
假设要画一个最简单的任意钝角三角形,想让钝角落在顶点 B。可以让:
- B 点固定在原点附近;
- BA 方向沿 x 轴正方向;
- BC 方向与 BA 的夹角为 120°。
也就是说,从 B 点出发,第一条边水平向右到 A,第二条边逆时针旋转 120° 偏向上方。由于内角大于 90°,A 和 C 会分布在 B 点右上方一块夹角较大的区域。具体坐标可以这样设计:
- B = (0, 0)
- A = (200, 0)
- C = (-60, 120)
为什么 C 点可以取负数横坐标?因为 BA 方向是 0°,BC 方向若想和 BA 形成 120° 夹角,可以取方向角 120°,则 C 点坐标为:
C_x = 150 * cos(120°) ≈ 150 * (-0.5) = -75 C_y = 150 * sin(120°) ≈ 150 * 0.866 ≈ 130也就是说,BC 边会伸向左上方。这时 A 在右下,C 在左上,三角形底边不一定是水平的,但钝角清晰可见。
我们不需要自己手算太多,可以交给 Python 的math库完成坐标计算。
5.2 完整代码
import turtle import math # 顶点B设为原点 b_x, b_y = 0, 0 # AB长度与方向:沿x轴正方向 ab_len = 220 a_x = b_x + ab_len a_y = b_y # BC长度与方向:与AB夹角为120度 bc_len = 160 angle_bc = math.radians(120) c_x = b_x + bc_len * math.cos(angle_bc) c_y = b_y + bc_len * math.sin(angle_bc) # 建立画布 screen = turtle.Screen() screen.setup(800, 600) screen.title("Python 海龟绘图:任意钝角三角形") t = turtle.Turtle() t.speed(5) t.pensize(2) t.pencolor("darkred") # 画三角形 points = [(a_x, a_y), (b_x, b_y), (c_x, c_y)] t.penup() t.goto(b_x, b_y) t.pendown() t.goto(a_x, a_y) t.goto(c_x, c_y) t.goto(b_x, b_y) # 标出顶点 t.penup() for label, (px, py) in zip(["A", "B", "C"], points): t.goto(px, py) t.dot(8, "blue") t.write(f" {label}({px:.0f},{py:.0f})", font=("Arial", 12, "bold")) t.hideturtle() screen.mainloop()5.3 优点与适用场景
这种“坐标三点连线”写法的最大优点是不需要考虑海龟左右转的顺序,只要三个顶点坐标计算正确,图形就一定正确。它适合画任意三角形,尤其适合后面要接“边长计算”“角度验证”“面积计算”等任务的场景。
不过缺点也很明显:它不太利于新手训练“转向角 = 外角”的思维。所以我的建议是:初学阶段先吃透方法一,理解海龟的转弯机制;在做题或画不规则图形时,再切换到方法二,减少调试成本。
6. 方法三:通用三段边 + 自动角度校验
前面两种方法都是给定了边长和角度。更进阶的做法是:只输入三条边的长度,让程序判断是否能构成三角形,再根据余弦定理计算出最大角,判断是不是钝角三角形;如果是,就自动按边长绘制。这种“校验 + 绘制 + 报告”的写法,已经比较接近实际工程里的小工具了。
6.1 余弦定理与钝角判断
已知三边 a、b、c,且 c 为最大边。如果能构成三角形,那么边 c 对应的最大角 C 满足:
cos(C) = (a² + b² - c²) / (2ab)判断标准:
- cos(C) < 0,说明 C > 90°,为钝角三角形;
- cos(C) = 0,说明 C = 90°,为直角三角形;
- cos(C) > 0,说明 C < 90°,如果最大角都是锐角,则为锐角三角形。
所以,程序中可以先排序获得最大边,再算出最大角,决定是否继续绘制。
6.2 完整代码
import turtle import math def judge_triangle(a, b, c): """判断三边能否构成三角形,并返回最大角。""" if a + b <= c or a + c <= b or b + c <= a: return None # 找出最大边 sides = sorted([a, b, c]) x, y, z = sides # 最大边 z 对应的角 cos_z = (x * x + y * y - z * z) / (2 * x * y) # 限制在[-1, 1],避免浮点误差 cos_z = max(-1.0, min(1.0, cos_z)) angle_z = math.degrees(math.acos(cos_z)) return angle_z def draw_obtuse_triangle(a, b, c): """ 用三边长度画三角形。 这里采用“先画底边 c,再用圆弧交会得到顶点”的思路, 确保三条边长度严格一致。 """ # 将底边放在x轴上,从(0,0)到(c,0) x1, y1 = 0, 0 x2, y2 = c, 0 # 顶点坐标由两个圆的交点决定: # 圆1:以A为圆心,半径b # 圆2:以B为圆心,半径a # 交点的x坐标 d = c cos_alpha = (b * b + d * d - a * a) / (2 * b * d) cos_alpha = max(-1.0, min(1.0, cos_alpha)) alpha = math.acos(cos_alpha) x3 = b * math.cos(alpha) y3 = b * math.sin(alpha) # 绘制 screen = turtle.Screen() screen.setup(800, 600) screen.title("Python 海龟绘图:三边自动绘制钝角三角形") t = turtle.Turtle() t.speed(5) t.pensize(2) t.penup() t.goto(x1, y1) t.pendown() t.goto(x2, y2) t.goto(x3, y3) t.goto(x1, y1) # 标注 for label, px, py in [("A", x1, y1), ("B", x2, y2), ("C", x3, y3)]: t.penup() t.goto(px, py) t.dot(6, "purple") t.write(f" {label}", font=("Arial", 13, "bold")) # 输出检验信息 max_angle = judge_triangle(a, b, c) t.penup() t.goto(0, -80) t.write(f"最大角 = {max_angle:.2f}°", font=("Arial", 14, "normal")) t.hideturtle() screen.mainloop() if __name__ == "__main__": # 输入三边 a = 180 b = 140 c = 260 max_angle = judge_triangle(a, b, c) if max_angle is None: print("无法构成三角形,请重新输入") else: print(f"最大角为:{max_angle:.2f}°") if max_angle > 90: print("这是钝角三角形,开始绘制...") draw_obtuse_triangle(a, b, c) else: print("这不是钝角三角形,不绘制")6.3 运行示例
例如输入a=180, b=140, c=260:
- 260 是最长边;
- cos 值约
(180² + 140² - 260²) / (2*180*140); - 计算结果最大角超过 90°,程序判定为钝角三角形;
- 海龟窗口会出现一个底边为 260 像素、最大角位于 C 点的三角形。
如果输入a=100, b=100, c=100:
- 最大角为 60°;
- 程序不会绘制,而是输出“这不是钝角三角形”。
这种自动判断加自动绘制的代码,适合后续扩展到其他三角形题型。只要把判断条件改一下,就能画锐角、直角、等腰、等边,灵活性比固定代码高很多。
7. 运行结果与验证方法
运行环境不同,窗口表现可能略有差异,但核心判断逻辑是一致的。建议通过以下三个维度验证图形是否正确。
7.1 从几何性质验证
钝角三角形必须满足:最大角的平方大于另外两边平方之和。
对三边 a、b、c,若 c 是最大边,且 a² + b² < c²,则角 C 大于 90°。
我们可以用一组数据手动验证。例如上面代码用的a=180, b=140, c=260:
a² + b² = 180² + 140² = 32400 + 19600 = 52000 c² = 260² = 67600因为 52000 < 67600,所以最大角 C 是钝角。这说明三边数据满足钝角三角形条件,代码里“不画图或画图”的判断是基于正确数学依据的。
7.2 从运行输出验证
在方法三中,控制台会输出:
最大角为:119.79° 这是钝角三角形,开始绘制...为什么不是精确的 120°?因为三边是 180、140、260 时,最大角本身就是约 119.79°,这是余弦定理计算的自然结果,不是绘制误差。如果想得到精确的 120°,可以反推出三边组合,或者直接使用方法一中的等腰三角形方案。
7.3 从窗口图形验证
肉眼观察时,最直观的判断是找出最大角。在绘制了辅助坐标或顶点标记时,可以看三点坐标位置:如果最大角在顶点 C,且 C 点向三角形内侧收缩得很“尖”,外侧开口很大,说明是钝角。如果无法确认,可以在代码里继续添加write()输出每个角的实际度数,也可以使用turtle.distance()算出各边长度后二次确认。
8. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 三角形没有闭合,第三条边回不到起点 | 转向角用成了内角,而不是外角;或边长不符合角度约束 | 打印每次转向的角度,手动计算三外角之和是否为 360° | 把转向角改成 180° - 内角;统一边长与角度之间的几何关系 |
| 想画钝角三角形,画出来却像锐角 | 原本要把内角设为 120°,但代码写成了left(120),结果内角只有 60° | 检查最大顶点的转向角 | 若想在顶点形成 120° 内角,海龟应转 60° |
| 想画特定边长的三角形,图形位置偏离窗口中心 | 起点坐标设置不合适,比如从 (0, 0) 向右侧画完很长的底边,导致图形偏右 | 观察窗口坐标系位置 | 起始点设到底边中点左侧,例如goto(-base/2, -80) |
| 窗口一闪而过 | 没有调用screen.mainloop()或turtle.done() | 检查代码最后是否有事件循环 | 在程序末尾添加turtle.done() |
| 绘制出来的钝角三角形不明显,角度接近 90° | 三个顶点角度设计差距太小,或三边接近勾股数 | 用余弦定理计算最大角 | 尽量让顶角在 100° 到 150° 之间,视觉效果更明显 |
报错math domain error | 余弦值超出 [-1, 1],通常因为三边无法构成三角形或存在浮点误差 | 检查三边是否满足两边之和大于第三边 | 先进行三角形合法性判断,再用max(-1.0, min(1.0, cos_value))限制范围 |
8.1 排查示例:画出来接近直角
假设你输入三边为a=200, b=150, c=250:
a² + b² = 40000 + 22500 = 62500 c² = 62500此时 a² + b² = c²,是直角三角形。很多初学同学看不出三边里藏着的 3:4:5 比例,还以为自己写的是钝角三角形。所以排查第一步永远是用平方关系计算,而不是靠肉眼看。
8.2 排查示例:交点跑到画面外
当你用方法三的圆交点法绘制时,如果第三点 y 坐标为负,图形会出现在 x 轴下方。此时看不到三角形,不是代码错误,而是方向和位置没有调整。可以给所有 y 坐标加一个偏移量,或者把底边起点移动到合适位置。
9. 三种画法如何选
| 画法 | 适用对象 | 核心考察点 | 适合场景 |
|---|---|---|---|
| 方法一:等腰钝角三角形 | 初学 turtle 的同学 | 外角与内角互为补角 | 刚学习海龟转弯、想理解“为什么不是 left(120)” |
| 方法二:坐标定点连线 | 有一定数学计算能力的同学 | 用余弦定理或方向角求坐标 | 想画任意钝角三角形,或者要批量生成不同位置三角形 |
| 方法三:三边自动判断绘制 | 想写通用判断逻辑的同学 | 余弦定理、浮点容错、函数封装 | 后续刷三角形系列题、希望代码可复用的同学 |
三道题做完后,你会有一个明显感受:海龟绘图的难点并不在绘图函数本身,而在“如何把几何约束转换为海龟的移动命令”。方法一要求把内角转成外角,方法二要求把角度转成坐标,方法三则要求把边长转成交点坐标。每一次转换,都是一次数学建模的小练习。
对于刚入门的海龟绘图系列,建议先用方法一多画几个钝角三角形,比如顶角为 100°、120°、150° 的情况,观察外角分别对应多少。然后试着用手算的方式画出顶角为 150° 的等腰三角形,你会发现代码里转的角是 30°,而底角外角对应 75°。只有亲手调过几次角度,才能真正建立“内角与外角互相补足”的直觉。
如果你正在刷“Python 海龟绘图 100 题”这样的系列题,可以给自己加一个小目标:把画三角形这个任务抽成一个函数,传入三个边长,返回是否是可画的钝角三角形,并调用 turtle 自动绘制。这样第 12 题不仅完成了,还能顺带覆盖锐角、直角三角形的判断逻辑,后面再遇到同类题目直接复用即可。
下一步值得继续深入的方向有两个:一是用海龟绘图结合循环,一次画出多个不同形态的三角形摆成图案;二是把面积计算、周长计算和角度验证都整合到同一个绘图类里,让图形标注与数据输出一起完成。无论选哪个方向,前提都是把今天这个“外角 = 180° - 内角”的底层关系吃透。