本文翻自:Allen B. Downey ——《Think Python》
原文链接:http://www.greenteapress.com/thinkpython/html/thinkpython005.html
翻译:Simba Gu

[自述:感觉从这一章开始算是有点“干货”了,很容易激起初学者的兴趣,想当年上学的时候就是老师随便写的一个循环语句让两个大于号“>>”沿着屏幕绕圈就像贪吃蛇一样,吸引了我并且让我入了写程序这个“坑”]

第四章

学习案例:接口设计

本章示例代码可以从这里下载:
http://thinkpython.com/code/polygon.py

4.1 TurtleWorld

  为了配合本书,我写了一个Swampy包,你可以从这里下载并按照上面的说明安装到你的系统:http://thinkpython.com/swampy
  包是一个模块的集合,TurtleWorld就是Swampy里面的一个模块,它提供了一些引导小海龟在屏幕上画线的函数集。你只要在系统中安装了Swampy包就可以导入TurtleWorld模块:
  

from swampy.TurtleWorld import *

  如果你下载了Swampy包,但是没有安装,你可以在Swampy的根目录调试程序,或者把该目录添加到Python可以搜索的路径中去,然后再这样导入TurtleWorldlike :
  

from TurtleWorld import *

  安装过程和Python搜索路径的设置,取决于你的系统,本书就不作具体描述,如有疑问请参考此链接:http://thinkpython.com/swampy
  建立一个文件mypolygon.py,输入下面的代码:
  

from swampy.TurtleWorld import *world = TurtleWorld()
bob = Turtle()
print bobwait_for_user()

  第一行代码表示从swampy包导入TurtleWorld 模块。接下来一行建立一个TurtleWorld 对象和Turtle对象分别赋值给变量world和bob。打印bob变量你会看到类似这样的结果:

<TurtleWorld.Turtle instance at 0xb7bfbf4c>

  这表示bob变量引用了TurtleWorld 模块的一个实例 Turtle。从上下文中可以看出,“实例”表示集合的一个成员,这里的Turtle实例可以是一组或者集合中的一个。
  这里的 wait_for_user 告诉TurtleWorld 等待用户下一步操作,尽管在这个案例中除了等用户关闭窗口之外并无其它。
  TurtleWorld 提供了一些turtle转向的函数:fd和bk用于前进和后退,lt和rt用于左右转弯。并且每一个Turtle对象都有一支“笔”,可以落下或提起,如果笔落下,当Turtle对象移动的时候就会留下痕迹。函数pu和pd分别表示“提笔”和“落笔”。
  下面的代码可以画一个直角(代码放在建立的bob对象和 wait_for_user 函数之间):  

fd(bob, 100)
lt(bob)
fd(bob, 100)

  第一行代码让bob向前移动100,第二行让它向左转弯。当你运行程序的时候,你就可以看到bob向东向北移动并留下了运行轨迹。
  请修改代码画一个正方形。在实现此功能之前请不要继续本章内容!

4.2 简单循环

  你可能写了这样的代码(此处省略了建立TurtleWorld 对象和调用wait_for_user 函数)

fd(bob, 100)
lt(bob)fd(bob, 100)
lt(bob)fd(bob, 100)
lt(bob)fd(bob, 100)

  我们还可以用for循环语句来实现重复的功能。请添加以下代码到mypolygon.py脚本文件并运行:

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

  你应该可以看到下面的结果:

Hello!
Hello!
Hello!
Hello!

  这个例子里面用到了for语句,后面我们还会看到更多。但是这足以让你重新编写画正方形的程序,下面就是for语句实现的代码:

for i in range(4):fd(bob, 100)lt(bob)

  for语句的语法类似函数定义,它有一个以冒号结尾的头和缩进的正文,正文内可以包含任意数量的语句。
  for语句有时被称为循环,因为执行流程经过正文处理之后又回到循环的顶部。在这个案例中,正文内容被执行了4次。
  这里的代码跟之前画正方形的代码有些不同,因为在画出了正方形之后又进行了一次转弯,这样会花费额外的处理时间,但是如果是重复的动作,这样会简化代码,而且for循环的这个版本让Turtle回到原点之后也恢复了初始的方向。

4.3 练习

  下面是TurtleWorld系列的一些练习,这些本来是为了好玩,但是其中也不乏一些编程思想。当你在练习的时候请思考一下重点是什么。
  本书提供了下面练习的解决方案,你可以先尝试一下,不要直接抄答案。

    1. 编写一个名为square的函数,它传递一个名为 t 的turtle对象参数,实现用turtle对象画一个正方形。编写一个函数调用,将bob作为参数传递给square,然后再次运行程序。

    2. 在square函数添加另一个名为length的参数。修改函数内容,实现所画正方形的边长度为length,然后修改函数调用,加入第二个参数,再次运行程序。使用一定长度范围的值来测试程序。

  3. 默认情况下,lt和rt函数进行90度旋转,但您可以提供第二个参数,指定角度的数量。例如,lt(bob, 45) 可以让bob向左旋转45度。复制一个square函数,把它的名字改成polygon。再添加另一个名为n的参数并修改polygon函数主体,使其绘制一个n边正多边形。提示:n边正多边形的外角是360/n 度。

  4. 编写一个名为circle的函数,该函数以turtle对象 t 和半径 r 为参数,通过调用具有适当长度和边数的多边形来绘制一个近似圆。用一定范围的 r 来测试函数。

  • 提示:求出圆的周长,确保 length * n = circumference。
  • 另一个提示:如果你觉得 bob 速度太慢,你可以通过改变bob.delay(移动间隔时间)来加快速度,以秒为单位,例如 bob.delay = 0.01。

  5. 制作一个更通用的circle 函数,添加一个额外的参数angle,用来决定画一个圆弧的哪个部分。以angle为单位,当angle=360时,circle 函数就会画一个完整的圆。

4.4 封装

  上文中的第一个练习要求将画正方形图形的代码放入函数定义中,然后调用函数,并将turtle作为参数传递。解决方案如下:

def square(t):for i in range(4):fd(t, 100)lt(t)square(bob)

  在最内层的语句,fd 和 lt 缩进两次以表名它们位于for循环中,而for循环位于函数定义中。函数调用行square(bob)与左侧空白齐平,因此这是for循环和函数定义的结尾。
  在函数内部,t 表示Turtle对象bob,因此 lt(t) 与 lt(bob) 具有相同的效果。那这里为什么不直接调用参数bob呢?这是因为这里的 t 可以是任何Turtle对象,而不仅仅是bob,因为你可以创建另一个Turtle对象并将它作为参数传递给square函数:

ray = Turtle()
square(ray)

  在函数中包装一段代码称为封装。封装的好处之一是它可以将一个名称附加到代码上,作为一种文档。另一个优点是,如果重用代码,调用函数两次比复制和粘贴主体更简单方便!

4.5 泛化

  接下来是给square函数添加一个参数length。实现代码如下:

def square(t, length):for i in range(4):fd(t, length)lt(t)square(bob, 100)

  向函数添加参数称为泛化,因为它使函数更通用:在以前的版本中,正方形的大小是相同的;在这个版本中,它可以是可变的。
  下一步也是泛化。polygon 函数是画出任意数量的正多边形,而不是正方形。实现代码如下:

def polygon(t, n, length):angle = 360.0 / nfor i in range(n):fd(t, length)lt(t, angle)polygon(bob, 7, 70)

  这段代码将绘制一个边长度为70的7边形。如果函数有多个数值参数,那将会很容易忘记它们是什么,或者它们应该处于什么顺序。在参数列表中包含参数的名称是合法的,有时是很有帮助的:

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

  这些被称为关键字参数,因为它们包含参数名作为“关键字”(不要与Python关键字,如while和def等混淆)。
  这种语法使程序更具可读性,还提醒了参数和参数是如何工作的:当您调用一个函数时,实参数被分配给形参。

4.6 接口设计

  下一步就是写以半径 r 为参数的circle函数。这里有一个简单的解决方案,就是用polygon 函数画一个50面的多边形。

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

  第一行计算圆的周长,公式为2π*r。因为我们使用到了pi值,所以需要导入math模块。按照惯例,通常import语句都位于脚本的开头。
  n是画一个圆需要的近似的线的段数,所以length是每个线段的长度。因此,polygon 函数绘制了一个50边的多边形,它近似于一个半径为 r 的圆。
  这个解决方案有一个限制就是 n 是常数,这意味着对于很大的圆,每一个线段太长,对于小的圆来说又浪费时间画很小的线段。因此,一个解决方案是把n作为参数来泛化这个函数。这将给用户(无论谁调用circle函数)更多的控制,但界面将会变得有些乱。
  函数的接口是如何使用它:参数是什么?函数可以做什么?返回值是多少?如果接口“尽可能简单,但不简单”,那么它就是“精炼”的。(爱因斯坦)
  在本例中,变量 r 属于接口,因为它指定了要绘制的圆。n 则不是,因为它是函数内部如何渲染圆的局部变量。
  因此,与其让界面混乱,还不如根据周长选择合适的 n 的值:

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

  现在画线的段数是(大约)circumference/3,所以每个段的长度是(大约)3,每一段线的长度足够小,这样画出来的圆看起来才平滑,只要线的段数大到足够有效,就可以画出任何大小的圆。

4.7 重构

  当我在写circle函数的时候我可以重用polygon函数,应为一个许多边的多边形就是一个近似的圆。但是圆弧却不能重用circle和polygon函数。
  有一个可选的方法就是复制一份polygon函数,再改成 arc 函数。改完之后大致如下:

def arc(t, r, angle):arc_length = 2 * math.pi * r * angle / 360n = int(arc_length / 3) + 1step_length = arc_length / nstep_angle = float(angle) / nfor i in range(n):fd(t, step_length)lt(t, step_angle)

  这个函数的后半部分看起来像polygon函数,但是我们不能在不改变接口的情况下重用polygon函数。我们可以泛化polygon函数以一个角度作为第三个参数,但polygon将不再是一个合适的函数名字! 我们可以用一个更通用的函数名称polyline:

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

  因此可以把polyline函数重新改写polygon函数和arc 函数:

def polygon(t, n, length):angle = 360.0 / npolyline(t, n, length, angle)def arc(t, r, angle):arc_length = 2 * math.pi * r * angle / 360n = int(arc_length / 3) + 1step_length = arc_length / nstep_angle = float(angle) / npolyline(t, n, step_length, step_angle)

  最后,我们可以用arc函数重写circle函数:

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

  这个过程——重新安排程序以改进功能接口并促进代码重用可以称为“重构”。在本例中,我们注意到在arc和polygon函数中有类似的代码,因此我们将其分解为polyline函数。
  如果我们提前规划代码,我们可能会首先编写polyline函数并避免重构,但通常在项目开始的时候,您对程序设计中所需要的接口还不够了解。只有在开始编写代码之后,您才会更好地理解问题。某种程度上来说,当你开始重构的函数的时候标志着你已经学会了一些东西了。

4.8 开发方案

  开发计划是一个编写程序的过程。我们在本案例研究中使用的过程是“封装和泛化”。这项工作的步骤如下:
  首先编写一个没有函数定义的小程序。
  一旦程序可以正常运行,再把它封装在一个函数中,并且给函数起个名字。
  通过添加适当的参数来拓展该函数。
  重复步骤1–3,直到你有一个函数的集合。复制并粘贴工作代码,以避免重复输入(和重新调试)。
  通过重构寻找改进程序的机会。例如,如果您在几个地方有类似的代码,考虑将其分解为适当的通用函数。
  这个过程是有一些缺点的——我们在本书的后面会有替代方案——如果你不知道如何将程序划分为函数,这也不影响你继续本书的学习。

4.9 文档字符串

  docstring是函数开头的一个字符串,用于解释接口(“doc”是“documentation”的缩写)。这里有一个例子:

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

  这个docstring是一个用三引号括起来的字符串,也称为多行字符串,因为三元引号允许字符串跨越多行。
  它很简洁,但是它包含了一些函数所需要的重要信息。它简明地解释了函数的作用(没有详细介绍它是如何完成的)。它解释了每个参数对函数行为的影响,以及每个参数应该是什么类型(如果不是很明显的话)。
  编写这种文档是接口设计的一个重要部分。设计良好的接口应该很容易解释;如果您在解释您的某个函数时遇到了困难,这意味着这个接口还可以再改进。

4.10 调试

  接口就像函数和调用者之间的契约。调用方同意提供某些参数,该函数同意执行某些工作。
  例如,polyline 函数需要四个参数:t 必须是Turtle对象,n是线段的数目,所以n必须是一个整数;length应该是一个正数;angle必须是一个数字,表示角度的意思。
  这些需求被称为先决条件,因为它们应该在函数开始执行之前准备好。相反,函数末尾的条件是后置条件。后置条件包括函数的预期效果(比如画线段)和任何副加作用(如移动Turtle或在TurtleWorld中进行其他修改)。
  先决条件是调用方的责任。如果调用方违反了一个(适当的文档化的)先决条件,并且函数不能正常工作,那问题就在函数调用的地方,而不是函数里面。

4.11 术语表

实例:
  一个集合中的成员。本章中的TurtleWorld是TurtleWorld集合的成员。
循环:
  程序中可以重复执行的部分。
封装:
  将语句序列转换为函数定义的过程。
概括:
  用适当的通用(如变量或参数)替换不必要的特定对象(如数字)的过程。
关键参数:
  包含参数名称作为“关键字”的参数。
接口:
  描述如何使用一个函数,包括参数的名称和描述以及返回值。
重构:
  修改工作程序的过程,以改进函数接口和代码的其他质量。
开发计划:
  编写程序的过程。
文档字符串:
  在函数定义中显示的用于记录函数接口的字符串。
先决条件:
  函数启动前调用方应该满足的需求。
后置条件:
  函数结束前应该满足的需求。

4.12 练习

练习 1
  从http://thinkpython.com/code/polygon.py下载本章下载本章中的代码。
  为polygon、arc 和circle函数编写适当的文档。
  绘制一个堆栈图,显示执行圆时程序的状态(bob,radius)。您可以手工进行算术或向代码中添加打印语句。
  第4.7节中弧的版本不是很精确,因为圆的线性近似总是在真圆之外。因此导致Turtle离正确的目的地还差了几个单位。我给出的解决方案减小此错误影响的方法。请阅读代码,看看它对您是否有意义。如果你画一个图表,你可能会看清除它是如何工作的。

图4.1

练习 2
  编写一组适当的通用函数,可以绘制如图4.1所示的图案。
  解决方案:
  http://thinkpython.com/code/flor.py
  http://thinkpython.com/code/polygon.py

图4.2

练习 3
  编写一组适当的通用函数,可以绘制如图4.2所示的形状。
  解决方案:http://thinkpython.com/code/pie.py

练习 4
  字母表中的字母可以用一定数量的基本元素构成,如垂直线和水平线以及一些曲线。设计一种字体,它可以用最少的基本元素绘制,然后编写绘制字母的函数。
  您需要为每个字母编写一个函数,并命名为draw_a, draw_b, ...等,并将您的函数放入名为letters.py 的文件。你可以从可以从 http://thinkpython.com/code/typewriter.py 下载一个下载一个“Turtle打字机”来帮助你测试你的代码。
  解决方案:
  http://thinkpython.com/code/letters.py
  http://thinkpython.com/code/polygon.py

练习 5
  请阅读请阅读 http://en.wikipedia.org/wiki/Spiral 上的相关文章;然后编写一个绘制阿基米德螺旋(或其他种类的程序)
  解决方案:
  http://thinkpython.com/code/spiral.py.

#英文版权  Allen B. Downey
#翻译中文版权  Simba Gu
#转载请注明出处

转载于:https://www.cnblogs.com/simba/p/9964118.html

我要翻译《Think Python》- 006 第四章 学习案例:接口设计相关推荐

  1. 【李刚-21天通关Python】第四章:函数

    [李刚-21天通关Python]第四章:函数 第四章:函数 函数入门与定义函数 多返回值函数与递归函数 关键字参数与参数默认值 参数收集和逆向参数收集 变量作用域 局部函数 实操:定义计算N的阶乘的函 ...

  2. 《Dreamweaver CS6 完全自学教程》笔记 第十四章:使用 CSS 设计网页

    文章目录 第十四章:使用 CSS 设计网页 14.1 CSS 样式表简介 14.2 CSS 的基本语法 14.3 伪类.伪元素以及样式表的层叠顺序 14.3.1 伪类和伪元素 14.3.2 样式表的层 ...

  3. C++程序设计教程(钱能)第四章 学习笔记

    C++程序设计教程(钱能)第四章 学习笔记 4.1 名词解释与操作符 4.1.1 名词解释 4.1.2 操作符汇总 4.1.3 操作符的说明 4.2 算数运算问题 4.2.1 周而复始的整数 4.2. ...

  4. Effective Java(第三版) 学习笔记 - 第四章 类和接口 Rule20~Rule25

    Effective Java(第三版) 学习笔记 - 第四章 类和接口 Rule20~Rule25 目录 Rule20 接口优于抽象类 Rule21 为后代设计接口 Rule22 接口只用于定义类型 ...

  5. Python基础——第四章:Python循环语句

    前言 本文是根据黑马程序员Python教程所作之笔记,目的是为了方便我本人以及广大同学们查漏补缺. 不想做笔记直接来我的频道.当然啦,自己的笔记才是最好的哦! PS:感谢黑马程序员! 教程链接:黑马程 ...

  6. 数字图像处理与MATLAB 第四章学习笔记

    第四章 图像复原与重建 图像复原技术主要目的是以预先确定的目标来改善图像,大部分属于客观处理,面向退化模型,并采用相反的过程进行处理,以便恢复出原图像. 图像增强技术基本上是一种探索性过程,即根据人类 ...

  7. 《鲁棒控制——线性矩阵不等式处理方法》(俞立)第二、三、四章学习笔记

    第二章   线性矩阵不等式  :非零向量,  或者的最大特征值小于0. 是凸集.(设V是数域P上的线性空间,W是V的一个非空子集,如果对W中任意两个向量a,b以及任意0<=c<=1,都有c ...

  8. 计算机网络第四章学习通题目及答案

    目录 分类的 IP 地址 IP地址与硬件地址 ARP与RARP协议 IP数据报的格式 划分子网 ​ 第七次练兵 分组转发算法 ​ CIDR(构造超网) ICMP报文及应用 内部网关协议RIP 分类的 ...

  9. java2 实用教程第五版 第四章课本案例及课后题

    第五天 java2 实用教程第五版 耿祥义 张跃平编著 第四章代码 代码1:课本P80 package java课本项目;import java.util.*;public class Example ...

最新文章

  1. 前后端分离后的前端时代
  2. C++语言程序设计视频教程_清华大学_郑莉(基础篇+进阶篇)
  3. 模p加法和模p乘法学习
  4. python数据库编程dbf_python读写dbf文件
  5. python django 是啥_python的django做什么的
  6. Spring面向方面的编程
  7. 今天pycharm不能正常使用了
  8. 哪些事是你当了大学老师之后才知道的?
  9. linux系统清理内存,如何清除linux的内存缓存,缓冲和交换空间
  10. java redis事务_Redis事务
  11. pandas处理mysql 展现wpf_Pandas DataFrame使用多列聚合函数
  12. 微信小程序项目实例——家居团购
  13. mysql结构改写为hbase表_sqlite数据库修改表结构
  14. npm install 报错sill pacote range manifest for hmac-drbg@^1.0.0 fetched
  15. 海王小姐姐教你微信如何三步实现多开,win7 win 8 win10都可用
  16. Tkinter Treeview tag_configure失效问题
  17. 伙伴系统之避免碎片--Linux内存管理(十六)
  18. jsp引入外部css或js不生效
  19. OPPO的低代码实践:InnerEye低代码大屏
  20. 巴菲特与搭档查理芒格手把手教你如何读财报,唯一一篇百看不厌炒股最实用文章

热门文章

  1. php时间戳转时间 jq,jQuery时间戳和日期相互转换操作示例
  2. Ext2文件系统—文件读写
  3. React里里面试准备
  4. 微星(MSI)B360M MORTAR迫击炮Win10系统安装
  5. archlinux 查询和删除软件
  6. C++之学生考勤系统
  7. mysql分表的原则_mysql分表规则(转)
  8. 未转变者服务器指令不掉落,未转变者死亡不掉落的指令 | 手游网游页游攻略大全...
  9. 常用计算机故障处理指令,计算机常见故障分析与维修.ppt
  10. 4u机架式服务器性能如何,兼顾成本与性能 4U机架式服务器再曝光