《Think Python 2e》学习精粹(四): 案例研究—接口设计

《Think Python 2e》学习精粹(四): 案例研究—接口设计



  • 本章将通过一个案例研究,介绍如何设计出相互配合的函数;

1、turtle 模块

  • turtle 模块提供的 turtle.Turtle 函数创建一个 类型为 Turtle 的对象,可以赋值给变量,例如 bob 、sam 或者 jack 等(这里选的都是拟人化的名字);
>>> import turtle
>>> bob = turtle.Turtle()
>>> type(bob)
<class 'turtle.Turtle'>
>>> print(bob)
<turtle.Turtle object at 0x000001C946C106D0>
  • 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)出现在调用者一方,而不是函数;
  • 如果满足了先决条件,没有满足后置条件,故障就在函数一方。

猜你喜欢

转载自blog.csdn.net/weixin_41217917/article/details/111402057