# Python 教程 4 — 从0到1，学习Turtle并写一个优秀的应用

## 1.turtle模块 #

```>>> import turtle
>>> bob = turtle.Turtle()```

```import turtle
bob = turtle.Turtle()
print(bob)
turtle.mainloop()```

`turtle` 模块（小写的t）提供了一个叫作 `Turtle` 的函数（大写的T），这个函数会创建一个 `Turtle` 对象，我们将其赋值给名为 `bob` 的变量。打印 `bob` 的话，会输出下面这样的结果：

`<turtle.Turtle object at 0xb7bfbf4c>`

`mainloop` 告诉窗口等待用户操作，尽管在这个例子中，用户除了关闭窗口之外，并没有其他可做的事情。

`bob.fd(100)`

`fd` 方法的实参是像素距离，所以实际前进的距离取决于你的屏幕。

`Turtle` 对象中你能调用的其他方法还包括：让它向后走的 `bk` ，向左转的 `lt` ，向右转的 `rt` 。 `lt` 和 `rt` 这两个方法接受的实参是角度。

```bob.fd(100)
bob.lt(90)
bob.fd(100)```

## 2.简单的重复 #

```bob.fd(100)
bob.lt(90)

bob.fd(100)
bob.lt(90)

bob.fd(100)
bob.lt(90)

bob.fd(100)```

```for i in range(4):
print('Hello!')```

```Hello!
Hello!
Hello!
Hello!```

```for i in range(4):
bob.fd(100)
bob.lt(90)```

`for` 语句有时也被称为循环（loop），因为执行流程会贯穿整个语句体，然后再循环回顶部。 在此例中，它将运行语句体四次。

## 3.练习 #

1. 写一个名为 `square` 的函数，接受一个名为 `t` 的形参，`t` 是一个海龟。 这个函数应用这只海龟画一个正方形。写一个函数调用，将 `bob` 作为实参传给 `square` ，然后再重新运行程序。
2. 给 `square` 增加另一个名为 `length` 的形参。 修改函数体，使得正方形边的长度是 `length` ，然后修改函数调用，提供第二个实参。 重新运行程序。用一系列 `length` 值测试你的程序。
3. 复制 `square` ，并将函数改名为 `polygon` 。 增加另外一个名为 `n` 的形参并修改函数体，让它画一个正n边形（n-sided regular polygon）。 提示：正n边形的外角是360/n360/n度。
4. 编写一个名为 `circle` 的函数，它接受一个海龟t和半径r作为形参， 然后以合适的边长和边数调用 `polygon` ，画一个近似圆形。 用一系列r值测试你的函数。提示：算出圆的周长，并确保 `length * n = circumference` 。
5. 完成一个更泛化（general）的 `circle` 函数，称其为 `arc` ，接受一个额外的参数 `angle` ，确定画多完整的圆。`angle` 的单位是度，因此当 `angle=360` 时， `arc` 应该画一个完整的圆。

## 4.封装 #

```def square(t):
for i in range(4):
t.fd(100)
t.lt(90)

square(bob)```

```alice = Turtle()
square(alice)```

## 5.泛化 #

```def square(t, length):
for i in range(4):
t.fd(length)
t.lt(90)

square(bob, 100)```

```def polygon(t, n, length):
angle = 360 / n
for i in range(n):
t.fd(length)
t.lt(angle)

polygon(bob, 7, 70)```

`polygon(bob, n=7, length=70)`

## 6.接口设计 #

```import math

def circle(t, r):
circumference = 2 * math.pi * r
n = 50
length = circumference / n
polygon(t, n, length)```

n是我们的近似圆中线段的条数， `length` 是每一条线段的长度。 这样 `polygon` 画出的就是一个50边形，近似一个半径为r的圆。

```def circle(t, r):
circumference = 2 * math.pi * r
n = int(circumference / 3) + 1
length = circumference / n
polygon(t, n, length)```

## 8.重构 #

```def arc(t, r, angle):
arc_length = 2 * math.pi * r * angle / 360
n = int(arc_length / 3) + 1
step_length = arc_length / n
step_angle = angle / n

for i in range(n):
t.fd(step_length)
t.lt(step_angle)```

```def polyline(t, n, length, angle):
for i in range(n):
t.fd(length)
t.lt(angle)```

```def polygon(t, n, length):
angle = 360.0 / n
polyline(t, n, length, angle)

def arc(t, r, angle):
arc_length = 2 * math.pi * r * angle / 360
n = int(arc_length / 3) + 1
step_length = arc_length / n
step_angle = float(angle) / n
polyline(t, n, step_length, step_angle)```

```def circle(t, r):
arc(t, r, 360)```

## 9.开发方案 #

1. 从写一个没有函数定义的小程序开始。
2. 一旦该程序运行正常，找出其中相关性强的部分，将它们封装进一个函数并给它一个名字。
3. 通过增加适当的形参，泛化该函数。
4. 重复1–3步，直到你有一些可正常运行的函数。 复制粘贴有用的代码，避免重复输入（和重新调试）。
5. 寻找机会通过重构改进程序。 例如，如果在多个地方有相似的代码，考虑将它分解到一个合适的通用函数中。

## 10.文档字符串 #

```def polyline(t, n, length, angle):
"""Draws n line segments with the given length and
angle (in degrees) between them.  t is a turtle.
"""
for i in range(n):
t.fd(length)
t.lt(angle)```

## 12.术语表 #

ends.后置条件（postconditions）：函数终止之前应该满足的条件。

```"""This module contains a code example related to
Think Python, 2nd Edition
by Allen Downey
http://thinkpython2.com
"""

from __future__ import print_function, division

import math
import turtle

def square(t, length):
"""Draws a square with sides of the given length.
Returns the Turtle to the starting position and location.
"""
for i in range(4):
t.fd(length)
t.lt(90)

def polyline(t, n, length, angle):
"""Draws n line segments.
t: Turtle object
n: number of line segments
length: length of each segment
angle: degrees between segments
"""
for i in range(n):
t.fd(length)
t.lt(angle)

def polygon(t, n, length):
"""Draws a polygon with n sides.
t: Turtle
n: number of sides
length: length of each side.
"""
angle = 360.0/n
polyline(t, n, length, angle)

def arc(t, r, angle):
"""Draws an arc with the given radius and angle.
t: Turtle
angle: angle subtended by the arc, in degrees
"""
arc_length = 2 * math.pi * r * abs(angle) / 360
n = int(arc_length / 4) + 3
step_length = arc_length / n
step_angle = float(angle) / n

# making a slight left turn before starting reduces
# the error caused by the linear approximation of the arc
t.lt(step_angle/2)
polyline(t, n, step_length, step_angle)
t.rt(step_angle/2)

def circle(t, r):
"""Draws a circle with the given radius.
t: Turtle
"""
arc(t, r, 360)

# the following condition checks whether we are
# running as a script, in which case run the test code,
# or being imported, in which case don't.

if __name__ == '__main__':
bob = turtle.Turtle()

# draw a circle centered on the origin
bob.pu()
bob.lt(90)
bob.pd()

# wait for the user to close the window
turtle.mainloop()```

## 练习题 #

### 习题 4-1 #

1. 画一个执行 `circle(bob, radius)` 时的堆栈图（stack diagram），说明程序的各个状态。你可以手动进行计算，也可以在代码中加入打印语句。
2. “重构”一节中给出的 `arc` 函数版本并不太精确，因为圆形的线性近似（linear approximation）永远处在真正的圆形之外。因此，`Turtle` 总是和正确的终点相差几个像素。我的答案中展示了降低这个错误影响的一种方法。阅读其中的代码，看看你是否能够理解。如果你画一个堆栈图的话，你可能会更容易明白背后的原理。

### 习题 4-2 #

```"""This module contains a code example related to
Think Python, 2nd Edition
by Allen Downey
http://thinkpython2.com
"""

from __future__ import print_function, division

import turtle

from polygon import arc

def petal(t, r, angle):
"""Draws a petal using two arcs.
t: Turtle
angle: angle (degrees) that subtends the arcs
"""
for i in range(2):
arc(t, r, angle)
t.lt(180-angle)

def flower(t, n, r, angle):
"""Draws a flower with n petals.
t: Turtle
n: number of petals
angle: angle (degrees) that subtends the arcs
"""
for i in range(n):
petal(t, r, angle)
t.lt(360.0/n)

def move(t, length):
"""Move Turtle (t) forward (length) units without leaving a trail.
Leaves the pen down.
"""
t.pu()
t.fd(length)
t.pd()

bob = turtle.Turtle()

# draw a sequence of three flowers, as shown in the book.
move(bob, -100)
flower(bob, 7, 60.0, 60.0)

move(bob, 100)
flower(bob, 10, 40.0, 80.0)

move(bob, 100)
flower(bob, 20, 140.0, 20.0)

bob.hideturtle()
turtle.mainloop()```

```"""This module contains a code example related to
Think Python, 2nd Edition
by Allen Downey
http://thinkpython2.com
"""

from __future__ import print_function, division

import math
import turtle

def square(t, length):
"""Draws a square with sides of the given length.
Returns the Turtle to the starting position and location.
"""
for i in range(4):
t.fd(length)
t.lt(90)

def polyline(t, n, length, angle):
"""Draws n line segments.
t: Turtle object
n: number of line segments
length: length of each segment
angle: degrees between segments
"""
for i in range(n):
t.fd(length)
t.lt(angle)

def polygon(t, n, length):
"""Draws a polygon with n sides.
t: Turtle
n: number of sides
length: length of each side.
"""
angle = 360.0/n
polyline(t, n, length, angle)

def arc(t, r, angle):
"""Draws an arc with the given radius and angle.
t: Turtle
angle: angle subtended by the arc, in degrees
"""
arc_length = 2 * math.pi * r * abs(angle) / 360
n = int(arc_length / 4) + 3
step_length = arc_length / n
step_angle = float(angle) / n

# making a slight left turn before starting reduces
# the error caused by the linear approximation of the arc
t.lt(step_angle/2)
polyline(t, n, step_length, step_angle)
t.rt(step_angle/2)

def circle(t, r):
"""Draws a circle with the given radius.
t: Turtle
"""
arc(t, r, 360)

# the following condition checks whether we are
# running as a script, in which case run the test code,
# or being imported, in which case don't.

if __name__ == '__main__':
bob = turtle.Turtle()

# draw a circle centered on the origin
bob.pu()
bob.lt(90)
bob.pd()

# wait for the user to close the window
turtle.mainloop()```

### 习题 4-3 #

```"""This module contains a code example related to
Think Python, 2nd Edition
by Allen Downey
http://thinkpython2.com
"""

from __future__ import print_function, division

import math
import turtle

def draw_pie(t, n, r):
"""Draws a pie, then moves into position to the right.
t: Turtle
n: number of segments
r: length of the radial spokes
"""
polypie(t, n, r)
t.pu()
t.fd(r*2 + 10)
t.pd()

def polypie(t, n, r):
"""Draws a pie divided into radial segments.
t: Turtle
n: number of segments
r: length of the radial spokes
"""
angle = 360.0 / n
for i in range(n):
isosceles(t, r, angle/2)
t.lt(angle)

def isosceles(t, r, angle):
"""Draws an icosceles triangle.
The turtle starts and ends at the peak, facing the middle of the base.
t: Turtle
r: length of the equal legs
angle: half peak angle in degrees
"""
y = r * math.sin(angle * math.pi / 180)

t.rt(angle)
t.fd(r)
t.lt(90+angle)
t.fd(2*y)
t.lt(90+angle)
t.fd(r)
t.lt(180-angle)

bob = turtle.Turtle()

bob.pu()
bob.bk(130)
bob.pd()

# draw polypies with various number of sides
size = 40
draw_pie(bob, 5, size)
draw_pie(bob, 6, size)
draw_pie(bob, 7, size)
draw_pie(bob, 8, size)

bob.hideturtle()
turtle.mainloop()```

### 习题 4-4 #

```"""This module contains a code example related to
Think Python, 2nd Edition
by Allen Downey
http://thinkpython2.com
"""

from __future__ import print_function, division

import turtle

from polygon import circle, arc

# LEVEL 0 PRIMITIVES
# fd, bk, lt, rt, pu, pd

def fd(t, length):
t.fd(length)

def bk(t, length):
t.bk(length)

def lt(t, angle=90):
t.lt(angle)

def rt(t, angle=90):
t.rt(angle)

def pd(t):
t.pd()

def pu(t):
t.pu()

# LEVEL 1 PRIMITIVES are simple combinations of Level 0 primitives.
# They have no pre- or post-conditions.

def fdlt(t, n, angle=90):
"""forward and left"""
fd(t, n)
lt(t, angle)

def fdbk(t, n):
"""forward and back, ending at the original position"""
fd(t, n)
bk(t, n)

def skip(t, n):
"""lift the pen and move"""
pu(t)
fd(t, n)
pd(t)

def stump(t, n, angle=90):
"""Makes a vertical line and leave the turtle at the top, facing right"""
lt(t)
fd(t, n)
rt(t, angle)

def hollow(t, n):
"""move the turtle vertically and leave it at the top, facing right"""
lt(t)
skip(t, n)
rt(t)

# LEVEL 2 PRIMITIVES use primitives from Levels 0 and 1
# to draw posts (vertical elements) and beams (horizontal elements)
# Level 2 primitives ALWAYS return the turtle to the original
# location and direction.

def post(t, n):
lt(t)
fdbk(t, n)
rt(t)

def beam(t, n, height):
"""Makes a horizontal line at the given height and return."""
hollow(t, n*height)
fdbk(t, n)
hollow(t, -n*height)

def hangman(t, n, height):
"""Makes a vertical line to the given height and a horizontal line
at the given height and then return.
This is efficient to implement, and turns out to be useful, but
it's not so semantically clean."""
stump(t, n * height)
fdbk(t, n)
lt(t)
bk(t, n*height)
rt(t)

def diagonal(t, x, y):
"""Makes a diagonal line to the given x, y offsets and return"""
from math import atan2, sqrt, pi
angle = atan2(y, x) * 180 / pi
dist = sqrt(x**2 + y**2)
lt(t, angle)
fdbk(t, dist)
rt(t, angle)

def vshape(t, n, height):
diagonal(t, -n/2, height*n)
diagonal(t, n/2, height*n)

def bump(t, n, height):
"""Makes a bump with radius n at height*n
"""
stump(t, n*height)
arc(t, n/2.0, 180)
lt(t)
fdlt(t, n*height+n)

"""
The letter-drawing functions all have the precondition
that the turtle is in the lower-left corner of the letter,
and postcondition that the turtle is in the lower-right
corner, facing in the direction it started in.
They all take a turtle as the first argument and a size (n)
as the second.  Most letters are (n) units wide and (2n) units
high.
"""

def draw_a(t, n):
diagonal(t, n/2, 2*n)
beam(t, n, 1)
skip(t, n)
diagonal(t, -n/2, 2*n)

def draw_b(t, n):
bump(t, n, 1)
bump(t, n, 0)
skip(t, n/2)

def draw_c(t, n):
hangman(t, n, 2)
fd(t, n)

def draw_d(t, n):
bump(t, 2*n, 0)
skip(t, n)

def draw_ef(t, n):
hangman(t, n, 2)
hangman(t, n, 1)

def draw_e(t, n):
draw_ef(t, n)
fd(t, n)

def draw_f(t, n):
draw_ef(t, n)
skip(t, n)

def draw_g(t, n):
hangman(t, n, 2)
fd(t, n/2)
beam(t, n/2, 2)
fd(t, n/2)
post(t, n)

def draw_h(t, n):
post(t, 2*n)
hangman(t, n, 1)
skip(t, n)
post(t, 2*n)

def draw_i(t, n):
beam(t, n, 2)
fd(t, n/2)
post(t, 2*n)
fd(t, n/2)

def draw_j(t, n):
beam(t, n, 2)
arc(t, n/2, 90)
fd(t, 3*n/2)
skip(t, -2*n)
rt(t)
skip(t, n/2)

def draw_k(t, n):
post(t, 2*n)
stump(t, n, 180)
vshape(t, 2*n, 0.5)
fdlt(t, n)
skip(t, n)

def draw_l(t, n):
post(t, 2*n)
fd(t, n)

def draw_n(t, n):
post(t, 2*n)
skip(t, n)
diagonal(t, -n, 2*n)
post(t, 2*n)

def draw_m(t, n):
post(t, 2*n)
draw_v(t, n)
post(t, 2*n)

def draw_o(t, n):
skip(t, n)
circle(t, n)
skip(t, n)

def draw_p(t, n):
bump(t, n, 1)
skip(t, n/2)

def draw_q(t, n):
draw_o(t, n)
diagonal(t, -n/2, n)

def draw_r(t, n):
draw_p(t, n)
diagonal(t, -n/2, n)

def draw_s(t, n):
fd(t, n/2)
arc(t, n/2, 180)
arc(t, n/2, -180)
fdlt(t, n/2, -90)
skip(t, 2*n)
lt(t)

def draw_t(t, n):
beam(t, n, 2)
skip(t, n/2)
post(t, 2*n)
skip(t, n/2)

def draw_u(t, n):
post(t, 2*n)
fd(t, n)
post(t, 2*n)

def draw_v(t, n):
skip(t, n/2)
vshape(t, n, 2)
skip(t, n/2)

def draw_w(t, n):
draw_v(t, n)
draw_v(t, n)

def draw_x(t, n):
diagonal(t, n, 2*n)
skip(t, n)
diagonal(t, -n, 2*n)

def draw_v(t, n):
skip(t, n/2)
diagonal(t, -n/2, 2*n)
diagonal(t, n/2, 2*n)
skip(t, n/2)

def draw_y(t, n):
skip(t, n/2)
stump(t, n)
vshape(t, n, 1)
rt(t)
fdlt(t, n)
skip(t, n/2)

def draw_z(t, n):
beam(t, n, 2)
diagonal(t, n, 2*n)
fd(t, n)

def draw_(t, n):
# draw a space
skip(t, n)

if __name__ == '__main__':

# create and position the turtle
size = 20
bob = turtle.Turtle()

for f in [draw_h, draw_e, draw_l, draw_l, draw_o]:
f(bob, size)
skip(bob, size)

turtle.mainloop()```

```"""This module contains a code example related to
Think Python, 2nd Edition
by Allen Downey
http://thinkpython2.com
"""

from __future__ import print_function, division

import math
import turtle

def square(t, length):
"""Draws a square with sides of the given length.
Returns the Turtle to the starting position and location.
"""
for i in range(4):
t.fd(length)
t.lt(90)

def polyline(t, n, length, angle):
"""Draws n line segments.
t: Turtle object
n: number of line segments
length: length of each segment
angle: degrees between segments
"""
for i in range(n):
t.fd(length)
t.lt(angle)

def polygon(t, n, length):
"""Draws a polygon with n sides.
t: Turtle
n: number of sides
length: length of each side.
"""
angle = 360.0/n
polyline(t, n, length, angle)

def arc(t, r, angle):
"""Draws an arc with the given radius and angle.
t: Turtle
angle: angle subtended by the arc, in degrees
"""
arc_length = 2 * math.pi * r * abs(angle) / 360
n = int(arc_length / 4) + 3
step_length = arc_length / n
step_angle = float(angle) / n

# making a slight left turn before starting reduces
# the error caused by the linear approximation of the arc
t.lt(step_angle/2)
polyline(t, n, step_length, step_angle)
t.rt(step_angle/2)

def circle(t, r):
"""Draws a circle with the given radius.
t: Turtle
"""
arc(t, r, 360)

# the following condition checks whether we are
# running as a script, in which case run the test code,
# or being imported, in which case don't.

if __name__ == '__main__':
bob = turtle.Turtle()

# draw a circle centered on the origin
bob.pu()
bob.lt(90)
bob.pd()

# wait for the user to close the window
turtle.mainloop()```

### 习题 4-5 #

```"""This module contains a code example related to
Think Python, 2nd Edition
by Allen Downey
http://thinkpython2.com
"""

from __future__ import print_function, division

import turtle

def draw_spiral(t, n, length=3, a=0.1, b=0.0002):
"""Draws an Archimedian spiral starting at the origin.
Args:
n: how many line segments to draw
length: how long each segment is
a: how loose the initial spiral starts out (larger is looser)
b: how loosly coiled the spiral is (larger is looser)
http://en.wikipedia.org/wiki/Spiral
"""
theta = 0.0

for i in range(n):
t.fd(length)
dtheta = 1 / (a + b * theta)

t.lt(dtheta)
theta += dtheta

# create the world and bob
bob = turtle.Turtle()
draw_spiral(bob, n=1000)

turtle.mainloop()```

1. 翻译：@bingjin
2. 校对：@bingjin
3. 参考：@carfly

## 推荐阅读#

##### Wolfram: ChatGPT是怎么实现的？为什么它这么有效？#
ChatGPT 能够自动生成类似于人类写作的文本，这一点非常引人注目，也令人意外。但它是如何实现的？为什么它能…
##### Python 比较两个时间序列在图形上是否相似#

​Python实用宝典 (pythondict.com)