《Think Python 2e》学习精粹(四): 案例研究—接口设计
本章将通过一个案例研究,介绍如何设计出相互配合的函数;
1、turtle 模块
turtle
模块提供的
turtle.Turtle
函数创建一个 类型为
Turtle
的对象,可以赋值给变量,例如 bob 、sam 或者 jack 等(这里选的都是拟人化的名字);
>>> import turtle
>>> bob = turtle.Turtle()
>>> type(bob)
>>> print(bob)
Turtle 对象有一系列
method
(方法)可供调用:
bob.fd(100) :向前走100(像素);
bob.bk(100):向后退100(像素);
bob.lt(90):左转90(度);
bob.rt(90):右转90(度);
bob.pu():抬笔;
bob.pd():落笔;
>>> import turtle
>>> sam = turtle.Turtle()
>>> sam.fd(100)
>>> sam.lt(90)
>>> sam.fd(100)
>>> sam.lt(90)
>>> sam.fd(100)
>>> sam.lt(90)
>>> sam.fd(100)
>>> turtle.mainloop()
2、简单的重复
for 语句示例:
for i in range(4):
bob.fd(100)
bob.lt(90)
for 语句有一个以冒号结尾的语句头(header)以及一个缩进的语句体(body), 语句体可以包含任意条语句;
for 语句执行流程会贯穿整个语句体,然后再循环回顶部,所以也被称为循环(loop);
与自定义函数不同,for 语句的语句体不需要以空行结束;
for i in range(4):
print('Hello, World!')
print('I am gsf.')
PS C:\Users\Administrator> python D:\WorkSpace\thinkpython2e\ex_char4_4.py
Hello, World!
Hello, World!
Hello, World!
Hello, World!
I am gsf.
3、练习
Turtle 对象可以有多个,这里创建了 bob、 tom、sam 和 jack 等四个 Turtle 对象;
pu、fd、bk、lt、rt、pu 等方法组合可以使得 Turtle 对象移位;
turtle.mainloop()
引入是为了避免Python脚本在命令行执行完毕后,Turtle 对象自动销毁;如果在命令行输入Python语句,则不需要;
import math
import turtle
bob = turtle.Turtle()
tom = turtle.Turtle()
sam = turtle.Turtle()
jack = turtle.Turtle()
# 第二个小题(画可设边长的正方形),先把 Turtle 对象做了移位(下同)
def square(t, length):
for i in range(4):
t.fd(length)
t.lt(90)
sam.pu()
sam.bk(350)
sam.pd()
square(sam,100)
# 第三个小题(画多边形)
def polygon(t, length, n):
for i in range(n):
t.fd(length)
t.lt(360 / n)
tom.pu()
tom.fd(300)
tom.pd()
polygon(tom, 40, 10)
# 第四个小题(画圆)
def circle(t, r):
circumference = 2 * math.pi * r
n = int(circumference / 3) + 1
length = circumference / n
polygon(t, length, n)
circle(bob, 100)
# 第五个小题(画弧线),先把函数 polygon 进行泛化
def polygon_arc(t, length, n, angle):
for i in range(int(n * angle / 360)):
t.fd(length)
t.lt(360 / n)
def arc(t, r, angle):
circumference = 2 * math.pi * r
n = int(circumference / 3) + 1
length = circumference / n
polygon_arc(t, length, n, angle)
jack.pu()
jack.rt(90)
jack.fd(300)
jack.lt(90)
jack.pd()
arc(jack,100,120)
turtle.mainloop()
4、封装
encapsulation(封装)
:将一部分代码包装在函数里;
封装的好处之一,为这些代码赋予一个名字, 这充当了某种文档说明;
另一个好处是,如果重复使用代码, 调用函数更加简洁;
5、泛化
泛化(generalization)
:为函数增加一个形参被称作泛化;
下面三个函数是不断泛化的过程:
def square(t):
for i in range(4):
t.fd(100)
t.lt(90)
square(bob)
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)
泛化使得函数更通用;
在实参列表中加入形参的名称,这些被称作
关键字实参(keyword arguments)
;
polygon(bob, n=7, length=70)
实参和形参的工作方式: 当调用函数时,实参被赋给形参;
6、接口设计
函数的
接口(interface)
是一份关于如何使用该函数的总结:形参是什么?函数做什么?返回值是什么?
接口让调用者避免处理不必要的细节,直接做自己想做的事,那么这个接口就是“干净的”,如下例中的函数,调用者只须输入圆的半径 r 和 Turtle 对象名称这两个实参;
import math
def circle(t, r):
circumference = 2 * math.pi * r
n = int(circumference / 3) + 1
length = circumference / n
polygon(t, n, length)
接口让调用者处理不必要的细节,这个接口就是“不干净的”,如下例中的函数,调用者需输入模拟圆的多边形的边数的实参 n ;
import math
def circle(t, r, n):
circumference = 2 * math.pi * r
length = circumference / n
polygon(t, n, length)
7、重构
重构(refactoring)
:重新整理一个程序以改进函数接口和促进代码复用的过程;
下面第二个函数就是在第一个的基础上重构而得,增加了形参 angle ,促进函数复用;
def polygon(t, length, n):
for i in range(n):
t.fd(length)
t.lt(360 / n)
def polygon_arc(t, length, n, angle):
for i in range(int(n * angle / 360)):
t.fd(length)
t.lt(360 / n)
8、开发方案
开发计划(development plan)
:是一种编写程序的过程;
此例中我们使用的过程是“封装和泛化”:
从写一个没有函数定义的小程序开始;
一旦该程序运行正常,找出其中相关性强的部分,将它们封装进一个函数并给它一个名字;
通过增加适当的形参,泛化该函数。
重复1–3步,直到你有一些可正常运行的函数。 复制粘贴有用的代码,避免重复输入(和重新调试);
寻找机会通过重构改进程序。 例如,如果在多个地方有相似的代码,考虑将它分解到一个合适的通用函数中;
事先不知道如何将程序分解为函数,这是个很有用办法;
9、文档字符串
文档字符串(docstring)
:位于函数开始位置的一个字符串, 解释了函数的接口;
所有的文档字符串都是三重引号(triple-quoted)字符串,也被称为多行字符串;
def polygon_arc(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)
简要,包括了他人使用此函数时需要了解的关键信息:
说明该函数做什么(不介绍背后的具体细节);
解释每个形参对函数的行为有什么影响;
每个形参应有的类型 (如果它不明显的话);
写文档是接口设计中很重要的一部分;
10、调试
函数调用时对实参的要求被称作
先决条件(preconditions)
, 因为它们应当在函数开始执行之前成立(true);
函数结束时的条件是
后置条件(postconditions)
:函数预期的效果(如画线段)及任何其他附带效果 (如 Turtle移动、转向或者做其它改变);
如果调用者违反一个先决条件,导致函数没有正确工作,则故障(bug)出现在调用者一方,而不是函数;
如果满足了先决条件,没有满足后置条件,故障就在函数一方。