前两篇文章基本涵盖了 turtle 的大部分功能,同时也借由对 turtle 功能的展示,厘清了 Python 的一些语法特点,以利于新手入门。但是短短几个例子,阐述得还是有限,这里再展开两个知识点,一方面对 turtle 做个补遗,另一方面把 Python 语法的大框架过完一遍。
第一个是画椭圆。上一节中描述了如何用 turtle 画一个圆,或者是一段弧线,但是在很多图形中需要用到椭圆,如何画出一段优美的椭圆,是本篇的第一个知识点。
上节中有提到 turtle 中的 circle() 方法,其核心就是割圆术,也就是用正多边形来模拟一个圆。我们知道,正 8 边形比正 6 边形肯定要更接近一个圆,正 16 边形比正 8 边形又更接近一个圆,如果我们能画出一个正 120 边形,或者正 360 边形的话,那是非常接近一个圆的。下面就沿着这个思路,来画一个正 120 边形。不用说,在普通个人电脑上,“正 120 边形”在我们眼里肯定它就是一个“圆”了。上代码:
import turtle as t
t.pendown()
t.setheading(90) # 朝上(正北方向)
for j in range(120): # 重复执行 120 次
t.forward(3) # 移动 3 个单位
t.left(3) # 左转 3 度
t.penup()
t.done()
运行这个例子,可以看到 turtle 从原点出发,按逆时针方向画了一个圆。如果修改 forward() 中的参数,可以画出不同半径的圆。
这个画法跟 circle() 本质上没有区别。但是,却给了我们更大的自由度,来操控这段曲线,例如,修改代码如下:
import turtle as t
t.pendown()
t.setheading(90)
for j in range(60): # 重复执行 60 次
t.forward(3)
t.left(3)
t.penup()
t.done()
将重复运行的次数改为 60 次,每次还是转动 3 度,我们就可以得到一段 60*3=180 度的弧线。在不同的角度区间内,修改画弧的速度,也即修改 forward() 走的快慢,我样就可以得到一段椭圆弧,看代码:
import turtle as t
t.pendown()
t.setheading(90)
len = 1 # 设置初始走的速度为 1
for j in range(60):
if j < 30: # 当 j <30,也就是画前一半的弧线
len += 0.2 # 让速度越走越快
else: # 画后一半弧线
len -= 0.2 # 让速度越走越慢
t.forward(len)
t.left(3)
t.penup()
t.done()
运行这段代码,可以看到 turtle 画出了一段椭圆弧。能画成椭圆弧的关键是 if-else 条件语言的应用。if-else 属于分支语句,跟前面学过的顺序、循环共同构成 Python 语言的三大控制结构。在这个例子中,我们一共画 60 步弧线,在前 30 步,让画弧的速度由慢到快,后 30 步,速度由快到慢,这样不匀速的画法,就形成了一条椭圆弧。
接下来完善这段代码,画出一个完整的椭圆来:
import turtle as t
t.pendown()
t.setheading(90)
len = 1
for k in range(2): # 将相同的动作重复做一遍
for j in range(60):
if j < 30:
len += 0.2
else:
len -= 0.2
t.forward(len)
t.left(3)
t.penup()
t.done()
运行这段代码,可以看到 turtle 画出了一个完美的椭圆。相对于上一个例子,我们只增加了一条语句,即“for k in range(2):”,也就是将画上一半弧的方法,在下一半上重复使用一次即可。当然,你也可以通过改变 if-else 的方法来实现,只会逻辑上要复杂一点。
从这里我们也可以看到,turtle 绘图用的方法还是比较简单,适合于初学者入门使用,基本上不涉及计算机图形学的内容,要真正好出漂亮和复杂的弧线,turtle 库还是不够。
第二个是用 turtle 实现递归绘图。
现实生活中,有很多图形是非常有规律性的,这样的图形如果使用递归算法来实现,程序就会非常简洁,运行效果也会很好。下面我们来用 turtle 画一棵树,感受一下 Python 中的递归算法和 turtle 的克隆功能。树的最大特点就是每个树干都会左右分叉成两枝,而每枝又会再次分叉,这样循环往复一直进行。我们先来画一个树干分叉的小例子:
import turtle
p = turtle.Pen() # 第一支画笔
p.penup()
p.goto(0, -200) # 移动到初始位置
p.setheading(90) # 向上(正北方向)
p.pensize(7)
p.pencolor(‘green’)
p.pendown() # 落笔
p.forward(200) # 画第一条树干
q = p.clone() # 克隆出第二支笔来
p.left(65) # 第一支笔往左转
q.right(65) # 第二支笔往右转
p.forward(200 * 0.65)
q.forward(200 * 0.65)
turtle.done()
运行这个小例子,可以看到,turtle 在界面上画出一个 Y 形的树支,这个就是我们递归的基础,后面所有的小树枝是都这样画出来。这里用到一个很重要的知识点,就是 clone() 方法,我们用 clone() 克隆出第二笔,以便于从树干分别往两边画。
接下来,改造上面的小例子,应用递归函数,让 turtle 帮我们不断的画出更多的树枝来,上代码:
#-*- coding:utf-8 –*-
#用递归函数实现 turtle 画一棵树。
#所有递归函数都可以转化为非递归来实现,
#如果需要非递归方法的代码,请加公众号:see_goal 留言“turtle 画树”
import turtle
p = turtle.Pen()
p.penup()
p.goto(0, -200)
p.setheading(90)
p.pensize(7)
p.pencolor(‘green’)
p.pendown()
def branch(plist, len): # 自定义函数,画树枝
if (len > 15): # 递归的退出条件
list = [] # 新画笔列表
for p in plist: # 遍历旧画笔列表
p.forward(len)
q = p.clone()
p.left(65)
q.right(65)
list.append(p) # 存入新画笔列表
list.append(q) # 存入新画笔列表
branch(list, len * 0.65) # 递归,list 为新画笔列表,树枝长 65%
branch([p], 200)
turtle.done()
运行这段代码,可以看到 turtle 在界面上递归的画出一棵树。
这棵树上的每一个小箭头,都代表着一个 turtle 的 Pen 对象。也就是说,我们通过不断的克隆 Pen,来实现让每个树枝都能向左右两边伸展。而每一次伸展的长度都是上一个树枝的 0.65 倍,也就是越伸越短。当短到 <15 时,递归结束。每次克隆出的新 Pen,都通过 list.append() 方法存到列表中,传递给下一次调用,这样就给人一种树枝不断发芽生长的动画效果。