diff --git a/.gitignore b/.gitignore index 1a366fb..51fe76d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,10 @@ # Node rules: -## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +## Grunt intermediate storage (http://gruntjs.com/creating-plugins# storing-task-files) .grunt -## Dependency directory -## Commenting this out is preferred by some people, see -## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git +## Dependency directory +## Commenting this out is preferred by some people, see +## https://docs.npmjs.com/misc/faq# should-i-check-my-node_modules-folder-into-git node_modules # Book build output diff --git a/README.md b/README.md index 5e4db15..34aeb9d 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,10 @@ ======= -## 译者的话 +## 译者的话 这是一本很经典的Python入门教材,也是一本很适合初学者的编程入门书籍。网上有过一些翻译,不过我觉得都还是自己动手来尝试一下,这样更有利于深入了解和体验,所以就再造轮子了。 -## 作者的话 +## 作者的话 这是Think Python这本书的第二版,本次使用的是Python3,与Python2有很多不同,这些不同之处会有标注。如果你用Python2的话,还是建议你去阅读[上一个版本](http://www.greenteapress.com/thinkpython/index.html)。 读者可以到[亚马逊](http://amzn.to/Owtmjy)购买本书;或者下载 Think Python 2e [PDF格式的电子版.](http://www.greenteapress.com/thinkpython2/thinkpython2.pdf);也可以在线阅读 Think Python 2e [HTML网页版本](http://www.greenteapress.com/thinkpython2/html/index.html)(推荐这个,都是文字格式,更方便). @@ -24,14 +24,14 @@ 样例代码以及其他问题的解决可以到[这里](http://www.greenteapress.com/thinkpython2/code)找(具体样例的链接在书中就有)。 -## 简要介绍 +## 简要介绍 Think Python 这本书是面向初学者介绍Python编程。 首先介绍的是一些编程的基本内容,给出概念和解释,然后循序渐进地深入讲解每个概念。 复杂的部分,比如递归以及面向对象编程,这些都分成一个个小块,以多个章节的方式来逐步介绍。 -## 第二版的更新 +## 第二版的更新 * 开始用Python3:书里面所有样例都用Python3来实现,参考代码也都做了升级,用Python2或者3都能运行。 @@ -48,21 +48,21 @@ Think Python 这本书是面向初学者介绍Python编程。 其他由 Allen Downey 编写的自-和谐-由书籍都可以在[Green Tea Press](http://greenteapress.com/)找到. -## 英文原版下载 +## 英文原版下载 * 编译好的PDF版本在这里下载:[PDF](http://www.greenteapress.com/thinkpython2/thinkpython2.pdf)。 * LaTeX代码在GitHub这里可以下载:[this GitHub repository](https://github.com/AllenDowney/ThinkPython2). -## 过往历史 +## 过往历史 第一版在[这里](http://www.greenteapress.com/thinkpython),是由剑桥大学出版社出版的,标题是 Python for Software Design. 可以到亚马逊去买到。 本书的原始版本由Green Tea Press 出版,标题为 How to Think Like a Computer Scientist: Learning with Python. 这个版本可以从[Lulu.com](http://lulu.com)这个网站找到。其他由 Allen Downey 编写的自由书籍都可以在Green Tea Press找到. # 前言 -## 本书的奇幻历史 +## 本书的奇幻历史 在1999年1月的时候呢,我正准备教一门Java的入门编程课。我当时已经教过三次了,受挫感很强。班上挂科率特别高,而且即使那些没挂科的学生编程的整体水平也特别低。 @@ -127,11 +127,11 @@ Think Python 这本书是面向初学者介绍Python编程。 我希望大家能享受学习这本书的过程,也希望这本书能帮助大家学习编程,并且让大家学会像计算机科学家一样思考,哪怕有一点点也好。 - 本书英文版原作者:Allen B. Downey(艾伦 唐尼) + 本书英文版原作者:Allen B. Downey(艾伦 唐尼) - Olin College 奥林商学院 + Olin College 奥林商学院 -## 致谢 +## 致谢 非常感谢Jeff Elkner,是他把我的Java教材翻译成了Python,才引起了这一项目的开始,并且也把Python语言介绍给我,它已经是我最喜欢的编程语言了。 也要感谢Chris Meyers,他对『如何像计算机科学家一样思考』的一些章节有贡献。 @@ -141,7 +141,7 @@ Think Python 这本书是面向初学者介绍Python编程。 最后还要感谢所有曾对本书早期版本做出过贡献的同学们,以及其他参与改错和提出建议的朋友们(列表如下)。 -### 贡献列表 +## # 贡献列表 有几百名读者,他们目光敏锐又思维迅捷,在过去的这些年里提供了各种建议,发现了各种错误。他们贡献和热情都是对本项目的巨大帮助。 diff --git a/chapter1.md b/chapter1.md index 778e63d..533df82 100644 --- a/chapter1.md +++ b/chapter1.md @@ -1,4 +1,5 @@ -#第一章 编程之路 +# 第一章 编程之路 + 本书的目的是教你学会像计算机科学家一样来思考。这种思考方式汇聚了数学、工程和自然科学的精华。计算机科学家像数学家一样,使用规范的语言来阐述思想(尤其是一些计算);像工程师一样设计、组装系统,并且在多重选择中寻找最优解;像自然科学家一样观察复杂系统的行为模式,建立猜想,测试预估的结果。 @@ -6,7 +7,7 @@ 在一定层面上,大家将通过编程本身来学习编程这一重要的技巧。在另外一些层面上,大家也将把编程作为实现一种目的的途径。这一目的会随着我们逐渐学习而越发清楚。 -##1.1 程序是什么? +## 1.1 程序是什么? 程序是一个指令的序列,来告诉机器如何进行一组运算。这种运算也许是数学上的,比如求解一组等式或者求多项式的根;当然也可以是符号运算,比如在文档中搜索和替换文字,或者一些图形化过程,比如处理图像或者播放一段视频。 @@ -25,7 +26,7 @@ 大家刚开始接触编程的话,可能还有点难以置信,核心内容仅仅上述这些而已。你用过的所有程序,无论多么复杂,都是由一些这样的指令组合而成的。因此大家可以把编程的过程理解成一个把庞大复杂任务进行拆分来解决的过程,分解到适合使用上述的基本指令来解决为止。 -##1.2 运行Python +## 1.2 运行Python 新手在刚接触Python的时候遇到的困难之一就是必须在电脑上安装Python和相关的一些软件。如果你熟悉操作系统,并且还很习惯用命令行接口,那安装Python对你来说就没啥问题了。但对初学者来说,要求他们既要了解系统管理又要学习编程,就可能有些困难了。 @@ -38,11 +39,11 @@ Python现在有两个主要的分之,即Python2和Python3。如果你学过其 Python的解释器是一个读取并执行Python代码的程序。根据你的系统环境,你可以点击图标或者在命令行中输入python来运行解释器。它运行起来,你会看到类似这样的输出: ```python -Python 3.4.0 (default, Jun 19 2015, 14:20:21) +Python 3.4.0 (default, Jun 19 2015, 14:20:21) [GCC 4.8.2] on linux Type "help", "copyright", "credits" or "license" for more information. ->>> ->>> +>>> +>>> ``` 开头的三行包含了关于解释器和所在操作系统的信息,所以大家各自的情况可能有所不同。不过当你检查版本的时候,比如例子中的是3.4.0,使用3开头的,那就告诉你了,他运行的是Python3。你肯定也能猜到,如果开头的是2那就是Python2咯。 @@ -56,7 +57,7 @@ Type "help", "copyright", "credits" or "license" for more information. ``` 现在你已经做好开始学习Python的准备了。现在我估计你应该已经知道怎么来启动Python解释器和运行Python代码了。 -##1.3 第一个程序 +## 1.3 第一个程序 传统意义上,大家学一门新编程语言要写的第一个程序都被叫做『Hello,World!』,因为这第一个程序就用来显示这个词组『Hello,World!』。在Python中,是这样实现的: @@ -77,7 +78,7 @@ Type "help", "copyright", "credits" or "license" for more information. 这个区别以后会理解更深入,现在说这点就够了。 -##1.4 运算符 +## 1.4 运算符 在『Hello,World!』之后,下一步就是运算了。Python提供了运算符,就是一些用来表示例如加法、乘法等运算的符号了。 @@ -125,7 +126,7 @@ Type "help", "copyright", "credits" or "license" for more information. 我在本书中不会涉及到位运算,但你可以在下面这个链接里面读一下来了解:[Wiki](http://wiki.python.org/moin/BitwiseOperators)。 -##1.5 值和类型 +## 1.5 值和类型 值就是一个程序操作的基本对象之一,比如一个字母啊,或者数字。刚刚我们看到了一些值的例子了,比如2,42.0,还有那个字符串『Hello,World!』 @@ -175,7 +176,7 @@ Type "help", "copyright", "credits" or "license" for more information. 出乎意料吧,Python把逗号当做了分隔三个整形数字的分隔符了。我们以后再对这种序列进行讨论。 -##1.6 公式语言和自然语言 +## 1.6 公式语言和自然语言 自然语言就是人说的语言,比如英语、西班牙语、法语,当然包括中文了。他们往往都不是人主动去设计出来的(当然,人会试图去分析语言的规律),自然而然地发生演进。 @@ -189,7 +190,7 @@ Type "help", "copyright", "credits" or "license" for more information. 第二个语法规则是代号必须有严格的组合结构。3 += 3这个式子数学上错误就因为虽然这些符号都是数学符号,但不能把加号等号放一起。类似地,化学方程式中要先写元素名字后写个数,而不是反着。 - This is @ well-structured Engli$h sentence with invalid t*kens in it. This sentence all valid tokens has, but invalid structure with. + This is @ well-structured Engli$h sentence with invalid t*kens in it. This sentence all valid tokens has, but invalid structure with. 这句英语的单词和结构都有错误,大家还是能看懂的哈。(译者注,作者故意这样写,来表明人类的自然语言容错率高。) @@ -198,36 +199,36 @@ Type "help", "copyright", "credits" or "license" for more information. 虽然公式语言和自然语言有很多共同特征,比如代号、结构、语法这些元素,但差别还是显著的,比如: -* 二义性 ambiguity: +* 二义性 ambiguity: 自然语言充满二义性,也就是歧义了,人们有时候用上下文线索或者其他信息来帮助处理这种情况。公式语言被设计为尽量不具有二义性,这就意味着一个语句往往只有唯一的一种含义,与上下文无关。 -* 冗余性 redundancy: +* 冗余性 redundancy: 为了弥补歧义,减少误解,自然语言有很多冗余,结果就是经常有废话。公式语言要精简的多。 -* 文字修辞 literalness: +* 文字修辞 literalness: 自然语言充满习语和隐喻等。比如我说 "The penny dropped", 可能并不是字面意思说硬币掉了(这个俚语意思是过了一会终于弄明白了)。公式语言的意思严格精准。 咱们大家都是说着自然语言长大的,要调节到公式语言有时候挺难的。这两者之间的差别有点像诗词和散文,但差别更大: ->* 诗词 Poetry: +>* 诗词 Poetry: 单词的运用要兼顾词义和押韵,诗的整体要有一定的意境或者情感上的共鸣。双关很常见,并且多是故意的。 ->* 散文 Prose: +>* 散文 Prose: 文字意思更重要,结构也有重要作用。相比诗词更好理解,但也有一定的双关语歧义。 ->* 程序 Programs: +>* 程序 Programs: 计算机程序的意义必须是无歧义和文采修饰的,能完全用代号和结构的方式进行解析。 公式语言比自然语言要更加密集,读起来也需要更长时间。公式语言的结构也非常重要,所以从头到尾或者从左到右未必就是最佳方式。大家应该学着动脑来解译程序,分辨代号,解析结构。最后要注意的就是在公式语言中,细节特别特别重要。拼写和符号的小错误对于自然语言来说没什么,但对公式语言来说就能带来大问题。 -##1.7 调试 +## 1.7 调试 程序员也会犯错的。由于很奇妙的原因,程序的错误被叫做bug,调试的过程就叫debug了。(译者注:一个传言是最早的计算机中经常有虫子进去导致短路之类的,清理虫子就成了常规调试的操作,流传至今。。。) @@ -241,7 +242,7 @@ Type "help", "copyright", "credits" or "license" for more information. 调试的过程挺烦人的,但这个本领很有价值,而且在编程之外的其他领域都有用武之地。在每一章的末尾,都会有这样的一段,我会给出一些关于调试方面的建议。希望能帮到大家! -##1.8 Glossary 术语列表 +## 1.8 Glossary 术语列表 problem solving: The process of formulating a problem, finding a solution, and expressing it. @@ -346,8 +347,8 @@ The process of finding and correcting bugs. >调试(debug):搜索和改正程序错误的过程。 -##1.9 练习 -###练习1 +## 1.9 练习 +## # 练习1 你读这本书的同时最好手边有台电脑,这样你就能把样例在电脑上随时运行来看看效果了。 diff --git a/chapter10.md b/chapter10.md index 0f621eb..1135325 100644 --- a/chapter10.md +++ b/chapter10.md @@ -1,8 +1,8 @@ -#第十章 列表 +# 第十章 列表 本章讲述的是 Python 里面最有用的一种内置类型:列表。你还能在本章中学到更多关于类的内容,你还会看到如果同一个对象有多个名字会有什么情况发生。 -##10.1 列表也是序列 +## 10.1 列表也是序列 和字符串差不多,列表是一系列的数值的序列。在字符串里面,这些值是字符;在列表里面,这些值可以是任意类型的。一个列表中的值一般叫做列表的元素,有时候也叫列表项。 @@ -34,7 +34,7 @@ [42, 123] [] ``` -##10.2 列表元素可修改 +## 10.2 列表元素可修改 读取列表元素的语法就如同读取字符串中的字符一样-用方括号运算符就可以了。方括号内的数字用来确定索引位置。一定要记住,Python 是从零开始计数的: @@ -57,7 +57,7 @@ 图10.1展示了 cheeses、numbers 和空列表的状态图: ________________________________________ -![Figure 10.1: State diagram](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPythonFigure10.1-%20State%20diagram.png) +![Figure 10.1: State diagram](./images/figure10.1.jpg) Figure 10.1: State diagram. ________________________________________ @@ -81,7 +81,7 @@ True >>> 'Brie' in cheeses False ``` -##10.3 遍历一个列表 +## 10.3 遍历一个列表 遍历一个列表中所有元素的最常用的办法就是 for 循环了。这种 for 循环和我们在遍历一个字符串的时候用的是一样的的语法: @@ -112,7 +112,7 @@ for x in []: ```Python ['spam', 1, ['Brie', 'Roquefort', 'Pol le Veq'], [1, 2, 3]] ``` -##10.4 列表运算符 +## 10.4 列表运算符 加号+运算符可以把列表拼接在一起: @@ -135,7 +135,7 @@ for x in []: 第一个例子中,[0]这个列表被重复四次。第二个例子把列表[1,2,3]重复了三次。 -##10.5 列表切片 +## 10.5 列表切片 切片操作符也可以用到列表上: @@ -165,7 +165,7 @@ for x in []: >>> t ['a', 'x', 'y', 'd', 'e', 'f'] ``` -##10.6 列表的方法 +## 10.6 列表的方法 Python 为操作列表提供了很多方法。比如,append 就可以在列表末尾添加一个新的元素: @@ -199,7 +199,7 @@ sort 把列表中的元素从低到高(译者注:下面的例子中是按照 ``` 大多数列表的方法都是无返回值的;这些方法都对列表进行修改,返回的是空。如果你不小心写出了一个 t=t.sort(),得到的结果恐怕让你很失望。 -##10.7 Map, filter, reduce 列表中最重要的三种运算 +## 10.7 Map, filter, reduce 列表中最重要的三种运算 要得到列表中所有值的综合,你可以用下面这样的一个循环来实现: @@ -263,7 +263,7 @@ only_upper 这样的运算也叫 filter(过滤器的意思),因为这种 常用的列表运算都可以表示成 map、filter 以及 reduce 的组合。 -##10.8 删除元素 +## 10.8 删除元素 从一个列表中删除元素有几种方法。如果你知道你要删除元素的索引,你就可以用 pop这个方法来实现: @@ -308,7 +308,7 @@ remove 的返回值是空。 ``` 和之前我们看到的一样,切片是含头不含尾,上面这个例子中从第『1』到第『5』个都被切片所选中,但包含开头的第『1』而不包含末尾的第『5』个元素。 -##10.9 列表和字符串 +## 10.9 列表和字符串 字符串是一系列字符的序列,而列表是一系列值的序列,但一个由字符组成的列表是不同于字符串的。要把一个字符串转换成字符列表,你可以用 list 这个函数: @@ -351,7 +351,7 @@ join 是与 split 功能相反的一个方法。它接收一个字符串列表 ``` 上面这个例子中,定界符是一个空格字符,所以 join 就在单词只见放一个空格。要想把字符聚集到一切而不要空格,你就可以用空字符串""作为一个定界符了。 -##10.10 对象和值 +## 10.10 对象和值 如果我们运行下面这种赋值语句: @@ -363,7 +363,7 @@ b = 'banana' 我们知道了 a 和 b 都是字符串,但我们不之道他们到底是不是同一个字符串。这就有可能有两种状态,如图10.2所示。 ________________________________________ -![Figure 10.2: State diagram](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPythonFigure10.2-%20State%20diagram.png) +![Figure 10.2: State diagram](./images/figure10.2.jpg) Figure 10.2: State diagram. ________________________________________ @@ -391,7 +391,7 @@ False 所以这时候的状态图就应该如图10.3所示的样子了。 ________________________________________ -![](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPythonFigure10.3-%20State%20diagram.png) +![Figure 10.3: State diagram](./images/figure10.3.jpg) Figure 10.3: State diagram. ________________________________________ @@ -402,7 +402,7 @@ ________________________________________ 目前位置,我们一直把『对象』和『值』来随意交换使用,但实际上更确切的说法是一个对象拥有一个值。如果你计算[1,2,3],你得到一个列表对象,整个列表对象的整个值是一个整数序列。如果另外一个列表有相同的元素,我们称之含有相同的值,但并不是相同的对象。 -##10.11 别名 +## 10.11 别名 如果 a 是一个对象了,然后你赋值 b=a,那么这两个变量都指向同一个对象: @@ -416,7 +416,7 @@ True 此时的状态图如图10.4所示。 ________________________________________ -![Figure 10.4: State diagram](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPythonFigure10.4-%20State%20diagram.png) +![Figure 10.4: State diagram](./images/figure10.4.jpg) Figure 10.4: State diagram. ________________________________________ @@ -446,7 +446,7 @@ b = 'banana' a 和 b 是否指向同一个字符就基本无所谓了,几乎不会有任何影响。 -##10.12 列表参数 +## 10.12 列表参数 当你传递一个列表给一个函数的时候,函数得到的是对该列表的一个引用。如果函数修改了列表,调用者会看到变化的。比如下面这个 delete_head 函数就从列表中删除第一个元素: @@ -465,7 +465,7 @@ def delete_head(t): 形式参数 t 和变量 letters 都是同一对象的别名。栈图如图10.5所示。 ________________________________________ -![Figure 10.5: Stack diagram](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPythonFigure10.5-%20Stack%20diagram.png) +![Figure 10.5: Stack diagram](./images/figure10.5.jpg) Figure 10.5: Stack diagram. ________________________________________ @@ -501,7 +501,7 @@ append 修改了列表,返回的是空。 ```Python def bad_delete_head(t): - t = t[1:] # WRONG! + t = t[1:] # WRONG! ``` 切片运算符创建了新的列表,然后赋值语句让 t 指向了这个新列表,但这不会影响调用者。 @@ -531,10 +531,10 @@ def tail(t): >>> rest ['b', 'c'] ``` -##10.13 调试 +## 10.13 调试 对列表或者其他可变对象,用得不小心的话,就会带来很多麻烦,需要好长时间来调试。下面是一些常见的陷阱,以及避免的方法: -###1. 大多数列表方法都修改参数,返回空值。这正好与字符串方法相反,字符串的方法都是返回一个新字符串,保持源字符串不变。 +## # 1. 大多数列表方法都修改参数,返回空值。这正好与字符串方法相反,字符串的方法都是返回一个新字符串,保持源字符串不变。 如果你习惯些下面这种字符串代码了: @@ -544,14 +544,14 @@ word = word.strip() 你估计就会写出下面这种列表代码: ```Python -t = t.sort() # WRONG! +t = t.sort() # WRONG! ``` 因为 sort 返回的是空值,所以对 t 的后续运算都将会失败。 在使用列表的方法和运算符之前,你应该好好读一下文档,然后在交互模式里面对它们进行一下测试。 -###2. 选一种方法并坚持使用。 +## # 2. 选一种方法并坚持使用。 列表使用的问题中很大一部分都是因为有太多方法来实现目的导致的。例如要从一个列表中删除一个元素,可以用 pop,remove,del 甚至简单的切片操作。 @@ -566,15 +566,15 @@ t += [x] 下面这就都是错的了: ```Python -t.append([x]) # WRONG! -t = t.append(x) # WRONG! -t + [x] # WRONG! -t = t + x # WRONG! +t.append([x]) # WRONG! +t = t.append(x) # WRONG! +t + [x] # WRONG! +t = t + x # WRONG! ``` 在交互模式下试试上面这些例子,确保你要理解它们的作用。要注意只有最后一个会引起运行错误,其他的三个都是合法的,但产生错误的效果。 -###3. 尽量做备份,避免用别名。 +## # 3. 尽量做备份,避免用别名。 如果你要用 sort 这样的方法来修改参数,又要同时保留源列表,你可以先做个备份。 @@ -597,7 +597,7 @@ t = t + x # WRONG! >>> t2 [1, 2, 3] ``` -##10.14 Glossary 术语列表 +## 10.14 Glossary 术语列表 list: A sequence of values. @@ -668,11 +668,11 @@ A character or string used to indicate where a string should be split. >定界符:一个字符或者字符串,用来确定字符分割时候的分界。 -##10.15 练习 +## 10.15 练习 你可以从 [这里](http://thinkpython2.com/code/list_exercises.py) 下载下面这些练习的样例代码。 -###练习1 +## # 练习1 写一个函数,名为 nested_sum,接收一系列的整数列表,然后把所有分支列表中的元素加起来。如下所示: @@ -681,7 +681,7 @@ A character or string used to indicate where a string should be split. >>> nested_sum(t) 21 ``` -###练习2 +## # 练习2 写一个函数,名为 cumsum,接收一个数字列表,然后返回累加的总和;也就是新列表的第 i 个元素就是源列表中前 i+1个元素的累加。如下所示: @@ -690,7 +690,7 @@ A character or string used to indicate where a string should be split. >>> cumsum(t) [1, 3, 6] ``` -###练习3 +## # 练习3 写一个函数,名为 middle,接收一个列表,返回一个新列表,新列表要求对源列表掐头去尾只要中间部分。如下所示: @@ -699,7 +699,7 @@ A character or string used to indicate where a string should be split. >>> middle(t) [2, 3] ``` -##练习4 +## 练习4 写一个函数,名为 chop,接收一个列表,修改这个列表,掐头去尾,返回空值。如下所示: @@ -709,7 +709,7 @@ A character or string used to indicate where a string should be split. >>> t [2, 3] ``` -###练习5 +## # 练习5 写一个函数,名为 is_sorted,接收一个列表作为参数,如果列表按照字母顺序升序排列,就返回真,否则返回假。如下所示: @@ -719,15 +719,15 @@ True >>> is_sorted(['b', 'a']) False ``` -###练习6 +## # 练习6 两个单词如果可以通过顺序修改来互相转换就互为变位词。写一个函数,名为 is_anagram,接收两个字符串,如果互为变位词就返回真。 -###练习7 +## # 练习7 写一个函数,名为 has_duplicates,接收一个列表,如果里面有重复出现的元素,就返回真。这个函数不能修改源列表。 -###练习8 +## # 练习8 这个练习也可以叫做生日悖论,你可以点击[这里](http://en.wikipedia.org/wiki/Birthday_paradox)来读一下更多背景知识。 @@ -735,13 +735,13 @@ False 你可以从 [这里](http://thinkpython2.com/code/birthday.py)下载我的样例代码。 -###练习9 +## # 练习9 写一个函数,读取文件 words.txt,然后建立一个列表,这个列表中每个元素就是这个文件中的每个单词。写两个版本的这样的函数,一个使用 append 方法,另外一个用自增运算的模式:t= t + [x]。看看哪个运行时间更长?为什么会这样? [样例代码](http://thinkpython2.com/code/wordlist.py) -###练习10 +## # 练习10 要检查一个单词是不是在上面这个词汇列表里,你可以使用 in 运算符,但可能会很慢,因为这个 in 运算符要从头到尾来搜索整个词汇表。 @@ -753,11 +753,11 @@ False [样例代码](http://thinkpython2.com/code/inlist.py). -###1练习11 +## # 1练习11 两个词如果互为逆序,就称它们是『翻转配对』。写一个函数来找一下在这个词汇表中所有这样的词对。[样例代码](http://thinkpython2.com/code/reverse_pair.py) -###1练习12 +## # 1练习12 两个单词,依次拼接各自的字母,如果能组成一个新单词,就称之为『互锁』。比如,shoe 和 **cold**就可以镶嵌在一起组成组成 s**c**h**o**o**l**e**d**。(译者注:shoe+**cold**= s**c**h**o**o**l**e**d** ) [样例代码](http://thinkpython2.com/code/interlock.py). 说明:这个练习受到了[这里一处例子](http://puzzlers.org)的启发。 diff --git a/chapter11.md b/chapter11.md index ddbd25d..f6fbe98 100644 --- a/chapter11.md +++ b/chapter11.md @@ -1,8 +1,8 @@ -#第十一章 字典 +# 第十一章 字典 本章要讲的内容是另外一种内置的类型,叫字典。字典是 Python 最有特色的功能之一;使用字典能构建出很多高效率又很优雅的算法。 -##11.1 字典是一种映射 +## 11.1 字典是一种映射 字典就像是一个列表一样,但更加泛化了,是列表概念的推广。在列表里面,索引必须是整数;而在字典里面,你可以用几乎任何类型来做索引了。 @@ -91,7 +91,7 @@ in 运算符在字典中和列表中有不同的算法了。对列表来说, 而对字典来说,Python 使用了一种叫做哈希表的算法,这就有一种很厉害的特性:in 运算符在对字典来使用的时候无论字典规模多大,无论里面的项有多少个,花费的时间都是基本一样的。我在13.4会解释一下其实现原理,不过你要多学几章之后才能理解对此的解释。 -##11.2 用字典作为计数器 +## 11.2 用字典作为计数器 假设你得到一个字符串,然后你想要查一下每个字母出现了多少次。你可以通过一下方法来实现: @@ -147,7 +147,7 @@ histogram的结果表明字母a 和 b 出现了一次,o 出现了两次,等 做个练习,用 get 这个方法,来缩写一下 histogram 这个函数,让它更简洁些。可以去掉那些 if 语句。 -##11.3 循环与字典 +## 11.3 循环与字典 如果你在 for 语句里面用字典,程序会遍历字典中的所有键。例如下面这个 print_hist 函数就输出其中的每一个键与对应的键值: @@ -167,7 +167,7 @@ a 1 p 1 r 2 t 1 o 1 明显这些键的输出并没有特定顺序。字典有一个内置的叫做 keys 的方法,返回字典中的所有键成一个列表,以不确定的顺序。做个练习,修改一下上面这个 print_hist 函数,让它按照字母表的顺序输出键和键值。 -##11.4 逆向查找 +## 11.4 逆向查找 给定一个字典 d,以及一个键 k,很容易找到对应的键值 v=d[k]。这个操作就叫查找。 @@ -200,7 +200,7 @@ def reverse_lookup(d, v): ```Python >>> k = reverse_lookup(h, 3) -Traceback (most recent call last): File "", line 1, in File "", line 5, in reverse_lookup ValueError +Traceback (most recent call last): File "", line 1, in File "", line 5, in reverse_lookup ValueError ``` 你自己 raise 一个异常的效果就和 Python 抛出的异常是一样的:程序会输出一个追溯以及一个错误信息。 @@ -210,13 +210,13 @@ raise 语句可以给出详细的错误信息作为可选的参数。如下所 ```Python >>> raise ValueError('value does not appear in the dictionary') -Traceback (most recent call last): File "", line 1, in ? +Traceback (most recent call last): File "", line 1, in ? ValueError: value does not appear in the dictionary ``` 逆向查找要比正常查找慢很多很多;如果要经常用到的话,或者字典变得很大了,程序的性能就会大打折扣。 -##11.5 字典和列表 +## 11.5 字典和列表 列表可以视作字典中的值。比如给你一个字典,映射了字符与对应的频率,你可能需要逆转一下;也就是建立一个从频率映射到字母的字典。因为可能有几个字母有同样的频率,在这个逆转字典中的每个值就应该是一个字母的列表。 @@ -248,7 +248,7 @@ def invert_dict(d): {1: ['a', 'p', 't', 'o'], 2: ['r']} ``` ________________________________________ -![Figure 11.1: State diagram](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPython11.1.png) +![Figure 11.1: State diagram](./images/figure11.1.jpg) Figure 11.1: State diagram. ________________________________________ @@ -261,7 +261,7 @@ ________________________________________ >>> t = [1, 2, 3] >>> d = dict() >>> d[t] = 'oops' -Traceback (most recent call last): File "", line 1, in ? TypeError: list objects are unhashable +Traceback (most recent call last): File "", line 1, in ? TypeError: list objects are unhashable ``` 我之前说过,字典是用哈希表(散列表)来实现的,这就意味着所有键都必须是散列的。 @@ -277,14 +277,14 @@ hash 是一个函数,接收任意一种值,然后返回一个整数。字典 (译者注:哈希表是一种散列表,相关内容译者知道的太少,所以这段翻译的质量大打折扣,实在抱歉。) -##11.6 Memos 备忘 +## 11.6 Memos 备忘 如果你试过了6.7中提到的斐波那契数列,你估计会发现随着参数增大,函数运行的时间也变长了。另外,运行时间的增长很显著。 要理解这是怎么回事,就要参考一下图11.2,图中展示了当 n=4的时候函数调用的情况。 ________________________________________ -![Figure 11.2: Call graph](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPython11.2.png) +![Figure 11.2: Call graph](./images/figure11.2.jpg) Figure 11.2: Call graph. ________________________________________ @@ -314,7 +314,7 @@ known 是一个用来保存已经计算斐波那契函数值的字典。开始 如果你运行这一个版本的斐波那契函数,你会发现比原来那个版本要快得多。 -##11.7 全局变量 +## 11.7 全局变量 在上面的例子中,known 这个字典是在函数外创建的,所以它属于主函数内,这是一个特殊的层。在主函数中的变量也叫全局变量,因为所有函数都可以访问这些变量。局部变量在所属的函数结束后就消失了,而主函数在其他函数调用结束后依然还存在。 @@ -333,7 +333,7 @@ def example1(): ```Python been_called = False def example2(): - been_called = True # WRONG + been_called = True # WRONG ``` 你可以运行一下,并不报错,只是 been_called 的值并不会变化。这个情况的原因是 example2这个函数创建了一个新的名为 been_called 的局部变量。函数结束之后,局部变量就释放了,并不会影响全局变量。 @@ -356,7 +356,7 @@ global 那句代码的效果是告诉解释器:『在这个函数内,been_ca ```Python count = 0 def example3(): - count = count + 1 # WRONG + count = count + 1 # WRONG ``` 运行的话,你会得到如下提示: @@ -395,7 +395,7 @@ def example5(): 全局变量很有用,但不能滥用,要是总修改全局变量的值,就让程序很难调试了。 -##11.8 调试 +## 11.8 调试 现在数据结构逐渐复杂了,再用打印输出和手动检验的方法来调试就很费劲了。下面是一些对这种复杂数据结构下的建议: @@ -409,7 +409,7 @@ def example5(): 再次强调一下,搭建脚手架代码的时间越长,用来调试的时间就会相应地缩短。 -##11.9 Glossary 术语列表 +## 11.9 Glossary 术语列表 mapping: A relationship in which each element of one set corresponds to an element of another set. @@ -512,36 +512,36 @@ A statement like global that tells the interpreter something about a variable. >声明:比如 global 这样的语句,用来告诉解释器变量的特征。 -##11.10 练习 -###练习1 +## 11.10 练习 +## # 练习1 写一个函数来读取 words.txt 文件中的单词,然后作为键存到一个字典中。键值是什么不要紧。然后用 in 运算符来快速检查一个字符串是否在字典中。 如果你做过第十章的练习,你可以对比一下这种实现和列表中的 in 运算符以及对折搜索的速度。 -###练习2 +## # 练习2 读一下字典中 setdefault 方法的相关文档,然后用这个方法来写一个更精简版本的 invert_dict 函数。 [样例代码](http://thinkpython2.com/code/invert_dict.py])。 -###练习3 +## # 练习3 用备忘的方法来改进一下第二章练习中的Ackermann函数,看看是不是能让让函数处理更大的参数。提示:不行。[样例代码](http://thinkpython2.com/code/ackermann_memo.py)。 -###练习4 +## # 练习4 如果你做过了第七章的练习,应该已经写过一个名叫 has_duplicates 的函数了,这个函数用列表做参数,如果里面有元素出现了重复,就返回真。 用字典来写一个更快速更简单的版本。[样例代码](http://thinkpython2.com/code/has_duplicates.py)。 -###练习5 +## # 练习5 一个词如果翻转顺序成为另外一个词,这两个词就为『翻转词对』(参见第五章练习的 rotate_word,译者注:作者这个练习我没找到。。。)。 写一个函数读取一个单词表,然后找到所有这样的单词对。[样例代码](http://thinkpython2.com/code/rotate_pairs.py)。 -###练习6 +## # 练习6 下面是一个来自[Car Talk](http://www.cartalk.com/content/puzzlers)的谜语: diff --git a/chapter12.md b/chapter12.md index cc02513..5c8fec6 100644 --- a/chapter12.md +++ b/chapter12.md @@ -1,10 +1,10 @@ -#第十二章 元组 +# 第十二章 元组 本章我们要说的是另外一种内置类型,元组,以及列表、字典和元组如何协同工作。此外还有一个非常有用的功能:可变长度的列表,聚集和分散运算符。 一点提示:元组的英文单词 tuple 怎么读还有争议。有人认为是发[tʌpəl] 的音,就跟『supple』里面的一样读音。但编程语境下,大家普遍读[tu:pəl],跟『quadruple』里一样。 -##12.1 元组不可修改 +## 12.1 元组不可修改 元组是一系列的值。这些值可以是任意类型的,并且用整数序号作为索引,所以可以发现元组和列表非常相似。二者间重要的区别就是元组是不可修改的。 @@ -93,7 +93,7 @@ True >>> (0, 1, 2000000) < (0, 3, 4) True ``` -##12.2 元组赋值 +## 12.2 元组赋值 对两个变量的值进行交换是一种常用操作。用常见语句来实现的话,就必须有一个临时变量。比如下面这个例子中是交换 a 和 b: @@ -134,7 +134,7 @@ split 的返回值是一个有两个元素的列表;第一个元素赋值给>>> domain 'python.org' ``` -##12.3 用元组做返回值 +## 12.3 用元组做返回值 严格来说,一个函数只能返回一个值,但如果这个值是一个元组,效果就和返回多个值一样了。例如,如果你想要将两个整数相除,计算商和余数,如果要分开计算 x/y 以及 x%y 就很麻烦了。更好的办法是同时计算这两个值。 @@ -168,7 +168,7 @@ def min_max(t): max 和 min 都是内置函数,会找到序列中的最大值或者最小值,min_max 这个函数会同时求得最大值和最小值,然后把这两个值作为元组来返回。 -##12.4 参数长度可变的元组 +## 12.4 参数长度可变的元组 函数的参数可以有任意多个。用星号*开头来作为形式参数名,可以将所有实际参数收录到一个元组中。例如 printall 就可以获取任意多个数的参数,然后把它们都打印输出: @@ -213,7 +213,7 @@ TypeError: sum expected at most 2 arguments, got 3 做个练习,写一个名为 sumall 的函数,让它可以接收任意数量的参数,返回总和。 -##12.5 列表和元组 +## 12.5 列表和元组 zip 是一个内置函数,接收两个或更多的序列作为参数,然后返回返回一个元组列表,该列表中每个元组都包含了从各个序列中的一个元素。这个函数名的意思就是拉锁,就是把不相关的两排拉锁齿连接到一起。 @@ -293,7 +293,7 @@ enumerate 函数的返回值是一个枚举对象,它会遍历整个成对序 0 a 1 b 2 c ``` -##12.6 词典与元组 +## 12.6 词典与元组 字典有一个名为 items 的方法,会返回一个由元组组成的序列,每一个元组都是字典中的一个键值对。 @@ -308,7 +308,7 @@ dict_items([('c', 2), ('a', 0), ('b', 1)]) ```Python >>> for key, value in d.items(): -... print(key, value) +... print(key, value) ... c 2 a 0 b 1 ``` @@ -349,20 +349,20 @@ for last, first in directory: 在状态图中表示元组的方法有两种。更详尽的版本会展示索引和元素,就如同在列表中一样。例如图12.1中展示了元组('Cleese', 'John') 。 ________________________________________ -![Figure 12.1: State diagram](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPython12.1.png) +![Figure 12.1: State diagram](./images/figure12.1.jpg) Figure 12.1: State diagram. ________________________________________ 但随着图解规模变大,你也许需要省略掉一些细节。比如电话字典的图解可能会像图12.2所示。 ________________________________________ -![Figure 12.2: State diagram](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPython12.2.png) +![Figure 12.2: State diagram](./images/figure12.2.jpg) Figure 12.2: State diagram. ________________________________________ 图中的元组用 Python 的语法来简单表示。其中的电话号码是 BBC 的投诉热线,所以不要给人家打电话哈。 -##12.7 由序列组成的序列 +## 12.7 由序列组成的序列 之前我一直在讲由元组组成的列表,但本章几乎所有的例子也适用于由列表组成的列表、元组组成的元组以及列表组成的元组。为了避免枚举所有的组合,咱们直接讨论序列组成的序列就更方便一些。 @@ -385,7 +385,7 @@ ________________________________________ 由于元组是不可修改的,所以不提供 sort 和 reverse 这样的方法,这些方法都只能修改已经存在的列表。但 Python 提供了内置函数 sorted,该函数接收任意序列,然后返回一个把该序列中元素重新排序过的列表,另外还有个内置函数 reversed,接收一个序列然后返回一个以逆序迭代整个列表的迭代器。 -##12.8 调试 +## 12.8 调试 列表、字典以及元组,都是数据结构的一些样例;在本章我们开始见识这些复合的数据结构,比如由元组组成的列表,或者包含元组作为键而列表作为键值的字典等等。符合数据结构非常有用,但容易导致一些错误,我把这种错误叫做结构错误;这种错误往往是由于一个数据结构中出现了错误的类型、大小或者结构而引起的。比如,如果你想要一个由一个整形构成的列表,而我给你一个单纯的整形变量(不是放进列表的),就会出错了。 @@ -437,7 +437,7 @@ ________________________________________ 如果你追踪自己的数据结构有困难,structshape这个模块能有所帮助。 -##12.9 Glossary 术语列表 +## 12.9 Glossary 术语列表 tuple: An immutable sequence of elements. @@ -478,12 +478,12 @@ An error caused because a value has the wrong shape; that is, the wrong type or >结构错误:由于一个值有错误的结构而导致的错误;比如错误的类型或者大小。 -##12.10 练习 -###练习1 +## 12.10 练习 +## # 练习1 写一个名为most_frequent的函数,接收一个字符串,然后用出现频率降序来打印输出字母。找一些不同语言的文本素材,然后看看不同语言情况下字母的频率变化多大。然后用你的结果与[这里](http://en.wikipedia.org/wiki/Letter_frequencies)的数据进行对比。[样例代码](http://thinkpython2.com/code/most_frequent.py)。 -###练习2 +## # 练习2 更多变位词了! @@ -503,11 +503,11 @@ An error caused because a value has the wrong shape; that is, the wrong type or 3. 在拼字游戏中,当你已经有七个字母的时候,再添加一个字母就能组成一个八个字母的单词,这就 TMD『bingo』 了(什么鬼东西?老外拼字游戏就跟狗一样,翻着恶心死了)。然后哪八个字母组合起来最可能得到 bingo?提示:有七个。(简直就是狗一样的题目,麻烦死了,这里数据结构大家学会了就好了。)[样例代码](http://thinkpython2.com/code/anagram_sets.py). -###练习3 +## # 练习3 两个单词,如果其中一个通过调换两个字母位置就能成为另外一个,就成了一个『交换对』。协议额函数来找到词典中所有的这样的交换对。提示:不用测试所有的词对,不用测试所有可能的替换方案。[样例代码](http://thinkpython2.com/code/metathesis.py)。 鸣谢:本练习受启发于[这里](http://puzzlers.org)的一个例子。 -###练习4 +## # 练习4 接下来又是一个[汽车广播字谜](http://www.cartalk.com/content/puzzlers): diff --git a/chapter13.md b/chapter13.md index c8406e6..e0349e0 100644 --- a/chapter13.md +++ b/chapter13.md @@ -1,14 +1,14 @@ -#第十三章 案例学习:数据结构的选择 +# 第十三章 案例学习:数据结构的选择 到现在为止,你已经学过 Python 中最核心的数据结构了,也学过了一些与之对应的各种算法了。如果你想要对算法进行深入的了解,就可以来读一下第十三章。但不读这一章也可以继续;无论什么时候读都可以,感兴趣了就来看即可。 本章通过一个案例和一些练习,来讲解一下如何选择和使用数据结构。 -##13.1 词频统计 +## 13.1 词频统计 跟往常一样,你最起码也得先自己尝试做一下这些练习,然后再看参考答案。 -###练习1 +## # 练习1 写一个读取文件的程序,把每一行拆分成一个个词,去掉空白和标点符号,然后把所有单词都转换成小写字母的。 @@ -18,12 +18,12 @@ ```Python >>> import string >>>string.punctuation -'!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~' +'!"# $%&'()*+,-./:;<=>?@[\]^_`{|}~' ``` 另外你也可以试试字符串模块的其他方法,比如 strip、replace 以及 translate。 -### 练习2 +## # 练习2 访问[古登堡计划网站] (http://gutenberg.org),然后下载一个你最喜欢的公有领域的书,要下载纯文本格式的哈。 @@ -36,15 +36,15 @@ 输出一下这本书中不重复的单词的个数。对比一下不同作者、不同地域的书籍。哪个作者的词汇量最丰富? -### 练习3 +## # 练习3 再接着修改程序,输出一下每本书中最频繁出现的20个词。 -### 练习4 +## # 练习4 接着修改,让程序能读取一个单词列表(参考9.1),然后输出一下所有包含在书中,但不包含于单词列表中的单词。看看这些单词中有多少是排版错误的?有多少是本应被单词列表包含的常用单词?有多少是很晦涩艰深的罕见词汇? -##13.2 随机数 +## 13.2 随机数 输入相同的情况下,大多数计算机程序每次都会给出相同的输出,这也叫做确定性。确定性通常是一件好事,因为我们都希望同样的运算产生同样的结构。但有时候为了一些特定用途,咱们就需要让计算机能有不可预测性。比如游戏等等,有很多很多这样的情景。 @@ -61,7 +61,7 @@ random 模块提供了生成假随机数的函数(从这里开始,咱们就 函数 random 返回一个在0.0到1.0的前闭后开区间(就是包括0.0但不包括1.0,这个特性在 Python 随处都是,比如序列的索引等等)的随机数。每次调用 random,就会得到一个很长的数列中的下一个数。如下这个循环就是一个例子了: ```Python -import random for i in range(10): +import random for i in range(10): x = random.random() print(x) ``` @@ -87,7 +87,7 @@ choice 函数可以用来从一个序列中随机选出一个元素: random 模块还提供了其他一些函数,可以计算某些连续分布的随机值,比如Gaussian高斯分布, exponential指数分布, gamma γ分布等等。 -### 练习5 +## # 练习5 写一个名为 choose_from_hist 的函数,用这个函数来处理一下11.2中定义的那个histogram函数,从histogram 的值当中随机选择一个,这个选择的概率按照比例来定。比如下面这个histogram: @@ -100,7 +100,7 @@ random 模块还提供了其他一些函数,可以计算某些连续分布的 你的函数就应该返回a 的概率为2/3,返回b 的概率为1/3 -##13.3 词频 +## 13.3 词频 你得先把前面的练习作一下,然后再继续哈。可以从[这里](http://thinkpython2.com/code/analyze_book1.py)下载我的样例代码。 @@ -162,7 +162,7 @@ print('Number of different words:', different_words(hist)) Total number of words: 161080 Number of different words: 7214 ``` -##13.4 最常用的单词 +## 13.4 最常用的单词 要找到最常用的词,可以做一个元组列表,每一个元组包含一个单词和该单词出现的次数,然后整理一下这个列表,就可以了。 @@ -193,21 +193,21 @@ for freq, word in t[:10]: ```Python The most common words are: -to 5242 -the 5205 -and 4897 -of 4295 -i 3191 -a 3130 -it 2529 -her 2483 -was 2400 -she 2364 +to 5242 +the 5205 +and 4897 +of 4295 +i 3191 +a 3130 +it 2529 +her 2483 +was 2400 +she 2364 ``` 如果使用 sort 函数的 key 参数,上面的代码还可以进一步简化。如果你好奇的话,可以进一步阅读一下[说明](https://wiki.python.org/moin/HowTo/Sorting) -##13.5 可选的参数 +## 13.5 可选的参数 咱们已经看过好多有可选参数的内置函数和方法了。实际上咱们自己也可以写,写这种有可选参数的自定义函数。比如下面就是一个根据词频数据来统计最常用单词的函数: @@ -239,7 +239,7 @@ print_most_common(hist, 20) 如果一个函数同时含有必需参数和可选参数,就必须在定义函数的时候,把必需参数全都放到前面,而可选的参数要放到后面。 -##13.6 字典减法 +## 13.6 字典减法 有的单词存在于书当中,但没有包含在文件 words.txt 的单词列表中,找这些单词就有点难了,你估计已经意识到了,这是一种集合的减法;也就是要从一个集合(也就是书)中所有不被另一个集合(也就是单词列表)包含的单词。 @@ -282,14 +282,14 @@ apartment 这些单词有的是名字或者所有格之类的。另外的一些,比如『rencontre』,都是现在不怎么常用的了。不过也确实有一些单词是挺常用的,挺应该被列表所包含的! -###练习6 +## # 练习6 -Python 提供了一个数据结构叫 set(集合),该类型提供了很多常见的集合运算。可以在19.5阅读一下,或者阅读一下[这里的官方文档](http://docs.python.org/3/library/stdtypes.html#types-set)。 +Python 提供了一个数据结构叫 set(集合),该类型提供了很多常见的集合运算。可以在19.5阅读一下,或者阅读一下[这里的官方文档](http://docs.python.org/3/library/stdtypes.html# types-set)。 写一个程序吧,用集合的减法,来找一下书中包含而列表中不包含的单词吧。 [样例代码](http://thinkpython2.com/code/analyze_book2.py)。 -##13.7 随机单词 +## 13.7 随机单词 要从词频数据中选一个随机的单词,最简单的算法就是根据已知的单词频率来将每个单词复制相对应的个数的副本,然后组建成一个列表,从列表中选择单词: @@ -316,11 +316,11 @@ def random_word(h): 4. 用该索引值来找到单词列表中对应的单词。 -###练习7 +## # 练习7 写一个程序,用上面说的算法来从一本书中随机挑选单词。[样例代码](http://thinkpython2.com/code/analyze_book3.py)。 -##13.8 马科夫分析法 +## 13.8 马科夫分析法 如果让你从一本书中随机挑选一些单词,这些单词都能理解,但估计难以成为一句话: @@ -353,7 +353,7 @@ Due to some ancient injury? 在这个例子中,前缀的长度总是两个单词,但你可以以任意长度的前缀来进行马科夫分析。 -###练习8 +## # 练习8 Markov analysis 马科夫分析: @@ -375,7 +375,7 @@ He was very clever, be it sweetness or be angry, ashamed or only amused, at such 你应该自己独立尝试一下这些练习,然后再继续;然后你可以下载[我的样例代码](http://thinkpython2.com/code/markov.py)。另外你可能需要下载 [《艾玛》这本书的文本文件](http://thinkpython2.com/code/emma.txt)。 -##13.9 数据结构 +## 13.9 数据结构 使用马科夫分析来生成随机文本挺有意思的,但这个练习还有另外一个要点:数据结构的选择。在前面这些练习中,你必须要选择以下内容: @@ -413,7 +413,7 @@ def shift(prefix, word): 最后再考虑一下:上文中,我已经暗示了,咱们选择某种数据结构,要兼顾分析和生成。但这二者是分开的步骤,所以也可以分析的时候用一种数据结构,而生成的时候再转换成另外一种结构。只要生成时候节省的时间胜过转换所花费的时间,相权衡之下依然是划算的。 -##13.10 调试 +## 13.10 调试 调试一个程序的时候,尤其是遇到特别严峻的问题的时候,有以下五个步骤一定要做好: @@ -451,7 +451,7 @@ def shift(prefix, word): 找到一个困难问题的解决方法,需要阅读、测试、分析,有时候还要后撤。如果你在某一步骤中卡住了,试试其他方法。 -##13.11 Glossary 术语列表 +## 13.11 Glossary 术语列表 deterministic: Pertaining to a program that does the same thing each time it runs, given the same inputs. @@ -482,8 +482,8 @@ Debugging by explaining your problem to an inanimate object such as a rubber duc >小黄鸭调试法:对一个无生命的对象来解释你的问题,比如小黄鸭之类的,这样来调试。描述清楚问题很有助于解决问题,所以虽然小黄鸭并不会理解 Python 也不要紧。 -##13.12 练习 -###练习9 +## 13.12 练习 +## # 练习9 单词的『排名』就是在一个单词列表中,按照出现频率而排的位置:最常见的单词就排名第一了,第二常见的就排第二,依此类推。 [Zipf定律](http://en.wikipedia.org/wiki/Zipf's_law) 描述了自然语言中排名和频率的关系。该定律预言了排名 r 与词频 f 之间的关系如下: diff --git a/chapter14.md b/chapter14.md index 3b311f0..d0aa43c 100644 --- a/chapter14.md +++ b/chapter14.md @@ -1,8 +1,8 @@ -#第十四章 文件 +# 第十四章 文件 本章介绍的内容是『持久的』程序,就是把数据进行永久存储,本章介绍了永久存储的不同种类,比如文件与数据库。 -##14.1 持久 +## 14.1 持久 目前为止我们见过的程序大多是很短暂的,它们往往只是运行那么一会,然后产生一些输出,等运行结束了,它们的数据就也都没了。如果你再次运行一个程序,又要从头开始了。 @@ -14,13 +14,13 @@ 另一种方法是把程序的状态存到数据库里面。在本章我会演示一种简单的数据库,以及一个 pickle 模块,这个模块大大简化了保存程序数据的过程。 -##14.2 读写文件 +## 14.2 读写文件 文本文件就是一系列的字符串,存储在一个永久介质中,比如硬盘、闪存或者光盘之类的东西里面。 在9.1的时候我们就看到过如何打开和读取一个文件了。 -要写入一个文件,就必须要在打开它的时候用『w』作为第二个参数(译者注:w 就是 wirte 的意思了): +要写入一个文件,就必须要在打开它的时候用『w』作为第二个参数(译者注:w 就是 write 的意思了): ```Python >>> fout = open('output.txt', 'w') @@ -51,7 +51,7 @@ open 函数会返回一个文件对象,文件对象会提供各种方法来处 ``` 如果不 close 这个文件,就要等你的程序运行结束退出的时候,它自己才关闭了。 -##14.3 格式运算符 +## 14.3 格式运算符 write 方法必须用字符串来做参数,所以如果要把其他类型的值写入文件,就得先转换成字符串才行。最简单的方法就是用 str函数: @@ -90,7 +90,7 @@ write 方法必须用字符串来做参数,所以如果要把其他类型的 >>> 'In %d years I have spotted %g %s.' % (3, 0.1, 'camels') 'In 3 years I have spotted 0.1 camels.' ``` -这就要注意力,如果字符串中格式化序列有多个,那个数一定要和后面的元组中元素数量相等才行。另外格式化序列与元组中元素的类型也必须一样: +这就要注意了,如果字符串中格式化序列有多个,那个数一定要和后面的元组中元素数量相等才行。另外格式化序列与元组中元素的类型也必须一样: ```Python >>> '%d %d %d' % (1, 2) @@ -101,9 +101,9 @@ TypeError: %d format: a number is required, not str 第一个例子中,后面元组的元素数量缺一个,所以报错了;第二个例子中,元组里面的元素类型与前面格式不匹配,所以也报错了。 -想要对格式运算符进行深入了解,可以点击[这里](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting)。然后还有一种功能更强大的替代方法,就是用字符串的格式化方法 format,可以点击[这里](https://docs.python.org/3/library/stdtypes.html#str.format)来了解更多细节。 +想要对格式运算符进行深入了解,可以点击[这里](https://docs.python.org/3/library/stdtypes.html# printf-style-string-formatting)。然后还有一种功能更强大的替代方法,就是用字符串的格式化方法 format,可以点击[这里](https://docs.python.org/3/library/stdtypes.html# str.format)来了解更多细节。 -##14.4 文件名与路径 +## 14.4 文件名与路径 文件都是按照目录(也叫文件夹)来组织存放的。每一个运行着的程序都有一个当前目录,也就是用来处理绝大多数运算和操作的默认目录。比如当你打开一个文件来读取内容的时候,Python 就从当前目录先来查找这个文件了。 @@ -169,7 +169,7 @@ os.path.join 接收一个目录和一个文件名做参数,然后把它们拼 os 模块还提供了一个叫 walk 的函数,与上面这个函数很像,功能要更强大一些。做一个练习吧,读一下文档,然后用这个 walk 函数来输出给定目录中的文件名以及子目录的名字。可以从[这里](http://thinkpython2.com/code/walk.py)下载我的样例代码。 -##14.5 捕获异常 +## 14.5 捕获异常 读写文件的时候有很多容易出错的地方。如果你要打开的文件不存在,就会得到一个 IOerror: @@ -209,7 +209,7 @@ Python 会先执行 try 后面的语句。如果运行正常,就会跳过 exce 这种用 try 语句来处理异常的方法,就叫异常捕获。上面的例子中,except 语句中的输出信息并没有什么用。一般情况,得到异常之后,你可以选择解决掉这个问题或者再重试一下,或者就以正常状态退出程序了。 -##14.6 数据库 +## 14.6 数据库 数据库是一个用来管理已存储数据的文件。很多数据库都以类似字典的形式来管理数据,就是从键到键值成对映射。数据库和字典的最大区别就在于数据库是存储在磁盘(或者其他永久性存储设备中),所以程序运行结束退出后,数据库依然存在。 @@ -263,7 +263,7 @@ for key in db: ```Python >>> db.close() ``` -##14.7 Pickle模块 +## 14.7 Pickle模块 dbm 的局限就在于键和键值必须是字符串或者二进制。如果用其他类型数据,就得到错误了。 @@ -301,9 +301,9 @@ False 有 pickle了,就可以把非字符串的数据也存到数据库里面了。实际上这种结合方式特别普遍,已经封装到一个叫shelve的模块中了。 -##14.8 管道 +## 14.8 管道 -大多数操作系统都提供了一个命令行接口,也被称作『shell』。Shell 通常提供了很多基础的命令,能够来搜索文件系统,以及启动应用软件。比如,在 Unix 下面,就可以通过 cd 命令来切换目录,用 ls 命令来显示一个目录下的内容,如果装了火狐浏览器,就可以输入 fireforx 来启动浏览器了。 +大多数操作系统都提供了一个命令行接口,也被称作『shell』。Shell 通常提供了很多基础的命令,能够来搜索文件系统,以及启动应用软件。比如,在 Unix 下面,就可以通过 cd 命令来切换目录,用 ls 命令来显示一个目录下的内容,如果装了火狐浏览器,就可以输入 firefox 来启动浏览器了。 在 shell 下能够启动的所有程序,也都可以在 Python 中启动,这要用到一个 pipe 对象,这个直接翻译意思为管道的对象可以理解为 Python 到操作系统的 Shell 进行通信的途径,一个 pipe 对象就代表了一个运行的程序。 @@ -330,7 +330,7 @@ None ``` 返回值是 ls 这个进程的最终状态;None 的意思就是正常退出(没有错误)。 -举个例子,大多数 Unix 系统都提供了一个教唆 md5sum 的函数,会读取一个文件的内容,然后计算一个『checksum』(校验值)。你可以点击[这里](http://en.wikipedia.org/wiki/Md5)阅读更多相关内容。 +举个例子,大多数 Unix 系统都提供了一个计算 md5sum 的函数,会读取一个文件的内容,然后计算一个『checksum』(校验值)。你可以点击[这里](http://en.wikipedia.org/wiki/Md5)阅读更多相关内容。 这个命令可以很有效地检查两个文件是否有相同内容。两个不同内容产生同样的校验值的可能性是很小的(实际上在宇宙坍塌之前都没戏)。 @@ -343,11 +343,11 @@ None >>> res = fp.read() >>> stat = fp.close() >>> print(res) -1e0033f0ed0656636de0d75144ba32e0 book.tex +1e0033f0ed0656636de0d75144ba32e0 book.tex >>> print(stat) None ``` -##14.9 编写模块 +## 14.9 编写模块 任何包含 Python 代码的文件都可以作为模块被导入使用。举个例子,假设你有一个名字叫 wc.py 的文件,里面代码如下: @@ -400,16 +400,16 @@ __name__ 是一个内置变量,当程序开始运行的时候被设置。如 警告:如果你导入了一个已经导入过的模块,Python 是不会有任何提示的。Python 并不会重新读取模块文件,即便该文件又被修改过也是如此。 -所以如果你想要重新加在一个模块,你可以用内置函数 reload,但这个也不太靠谱,所以最靠谱的办法莫过于重启解释器,然后再次导入该模块。 +所以如果你想要重新加载一个模块,你可以用内置函数 reload,但这个也不太靠谱,所以最靠谱的办法莫过于重启解释器,然后再次导入该模块。 -##14.10 调试 +## 14.10 调试 读写文件的时候,你可能会碰到空格导致的问题。这些问题很难解决,因为空格、跳表以及换行,平常就难以用眼睛看出来: ```Python >>> s = '1 2\t 3\n 4' >>> print(s) -1 2 3 +1 2 3 4 ``` 这时候就可以用内置函数 repr 来帮忙。它接收任意对象作为参数,然后返回一个该对象的字符串表示。对于字符串,该函数可以把空格字符转成反斜杠序列: @@ -428,7 +428,7 @@ __name__ 是一个内置变量,当程序开始运行的时候被设置。如 (译者注:译者这里也鼓励大家,一般的小工具,自己有时间有精力的话完全可以尝试着自己写一写,对自己是个磨练,也有利于对语言进行进一步的熟悉。这里再推荐一本书:Automate the Boring Stuff with,作者是 Al Sweigart。该书里面提到了很多常用的任务用 Python 来实现。) -##14.11 Glossary 术语列表 +## 14.11 Glossary 术语列表 persistent: Pertaining to a program that runs indefinitely and keeps at least some of its data in permanent storage. @@ -499,21 +499,21 @@ An object that represents a running program, allowing a Python program to run co >管道对象:代表了一个正在运行的程序的对象,允许一个 Python 程序运行命令并读取运行结果。 -##14.12 练习 -###练习1 +## 14.12 练习 +## # 练习1 写一个函数,名为 sed,接收一个目标字符串,一个替换字符串,然后两个文件名;读取第一个文件,然后把内容写入到第二个文件中,如果第二个文件不存在,就创建一个。如果目标字符串在文件中出现了,就用替换字符串把它替换掉。 如果在打开、读取、写入或者关闭文件的时候发生了错误了,你的程序应该要捕获异常,然后输出错误信息,然后再退出。[样例代码](http://thinkpython2.com/code/sed.py)。 -###练习2 +## # 练习2 如果你从 [这里](http://thinkpython2.com/code/anagram_sets.py)下载了我的样例代码,你会发现该程序创建了一个字典,建立了从一个有序字母字符串到一个单词列表的映射,列表中的单词可以由这些字母拼成。例如'opst'就映射到了列表 [’opts’, ’post’, ’pots’, ’spot’, ’stop’, ’tops’]. 写一个模块,导入 anagram_sets 然后提供两个函数:store_anagrams 可以把相同字母异序词词典存储到一个『shelf』;read_anagrams 可以查找一个词,返回一个由其 相同字母异序词 组成的列表。 [样例代码](http://thinkpython2.com/code/anagram_db.py)。 -###练习3 +## # 练习3 现在有很多 MP3文件的一个大集合里面,一定有很多同一首歌重复了,然后存在不同的目录或者保存的名字不同。本次练习的目的就是要找到这些重复的内容。 1. 首先写一个程序,搜索一个目录并且递归搜索所有子目录,然后返回一个全部给定后缀(比如.mp3)的文件的路径。提示:os.path 提供了一些函数,能用来处理文件和路径名称。 diff --git a/chapter15.md b/chapter15.md index 00f9c4b..e03eb91 100644 --- a/chapter15.md +++ b/chapter15.md @@ -1,11 +1,11 @@ -#第十五章 类和对象 +# 第十五章 类和对象 到目前为止,你应该已经知道如何用函数来整理代码,以及用内置类型来组织数据了。接下来的一步就是要学习『面向对象编程』了,这种编程方法中,用户可以自定义类型来同时对代码和数据进行整理。面向对象编程是一个很大的题目;要有好几章才能讲出个大概。 本章的样例代码可以在[这里](http://thinkpython2.com/code/Point1.py)来下载,练习题对应的样例代码可以在[这里](http://thinkpython2.com/code/Point1_soln.py)下载。 -##15.1 用户自定义类型 +## 15.1 用户自定义类型 我们已经用过很多 Python 的内置类型了;现在我们就要来定义一个新的类型了。作为样例,我们会创建一个叫 Point 的类,用于表示一个二维空间中的点。 @@ -58,7 +58,7 @@ class Point: 每一个对象都是某一个类的一个实例,所以『对象』和『实例』可以互换来使用。不过本章我还是都使用『实例』这个词,这样就更能体现出咱们在谈论的是用户定义的类型。 -##15.2 属性 +## 15.2 属性 用点号可以给实例进行赋值: @@ -73,7 +73,7 @@ class Point: 下面的图表展示了上面这些赋值的结果。用于展示一个类及其属性的状态图也叫做类图;比如图15.1就是一例。 ________________________________________ -![Figure 15.1: Object diagram](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPython15.1.png) +![Figure 15.1: Object diagram](./images/figure15.1.jpg) Figure 15.1: Object diagram. ________________________________________ @@ -120,7 +120,7 @@ print_point 这个函数就接收了一个点作为参数,然后显示点的 做个练习,写一个名为 distance_between_points 的函数,接收两个点作为参数,然后返回两点之间的距离。 -##15.3 矩形 +## 15.3 矩形 有时候一个类中的属性应该如何设置是很明显的,不过有的时候就得好好考虑一下了。比如,假设你要设计一个表示矩形的类。你要用什么样的属性来确定一个矩形的位置和大小呢?可以忽略角度;来让情况更简单一些,就只考虑矩形是横向的或者纵向的。 @@ -156,13 +156,13 @@ box.corner.y = 0.0 表达式 box.corner.x 的意思是,『到 box 指代的对象中,选择名为 corner 的属性;然后到这个点对象中,选取名为 x 的属性值。』 ________________________________________ -![Figure 15.2: Object diagram](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPython15.2.png) +![Figure 15.2: Object diagram](./images/figure15.2.jpg) Figure 15.2: Object diagram. ________________________________________ 图15.2展示了这个对象的状态图。一个类去作为另外一个类的属性,就叫做嵌入。 -##15.4 多个实例作返回值 +## 15.4 多个实例作返回值 函数返回实例。比如 find_center 就接收一个 Rectangle (矩阵)对象作为参数,然后以一个Point(点)对象的形式返回矩形中心位置的坐标所在点: @@ -181,7 +181,7 @@ def find_center(rect): >>> print_point(center) (50, 100) ``` -##15.5 对象可以修改 +## 15.5 对象可以修改 通过对一个对象的属性进行赋值就可以修改该对象的状态了。比如,要改变一个举行的大小而不改变位置,就可以只修改宽度和高度,如下所示: ```Python @@ -211,7 +211,7 @@ def grow_rectangle(rect, dwidth, dheight): 做个练习,写一个名为 move_rectangle 的函数,接收一个矩形和dx 与 dy 两个数值。函数要改变矩形所在位置,具体的改变方法为对左下角顶点坐标的 x 和 y 分别加上 dx 和 dy 的值。 -##15.6 复制 +## 15.6 复制 别名有可能让程序读起来有困难,因为在一个位置做出的修改有可能导致另外一个位置发生不可预知的情况。这样也很难去追踪指向一个对象的所有变量。 @@ -251,7 +251,7 @@ False True ``` ________________________________________ -![Figure 15.3: Object diagram](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPython15.3.png) +![Figure 15.3: Object diagram](./images/figure15.3.jpg) Figure 15.3: Object diagram. ________________________________________ 图15.3展示了此时的类图的情况。这种运算叫做浅复制,因为复制了对象与对象内包含的所有引用,但不复制内嵌的对象。 @@ -271,7 +271,7 @@ box3和 box 就是完全隔绝开,没有公用内嵌对象,彻底不会相 做个练习吧,写一个新版本的 move_rectangle,创建并返回一个新的矩形,而不是修改旧有的矩形。 -##15.7 调试 +## 15.7 调试 当你开始使用对象的时候,你就容易遇到一些新的异常。如果你试图读取一个不存在的属性,就会得到一个属性错误AttributeError: ```Python @@ -314,7 +314,7 @@ except AttributeError: ``` 这样写一些处理不同类型变量的函数就更容易了;关于这一话题的更多内容会在17.9中展开。 -##15.8 Glossary 术语列表 +## 15.8 Glossary 术语列表 class: A programmer-defined type. A class definition creates a new class object. @@ -360,8 +360,8 @@ A diagram that shows objects, their attributes, and the values of the attributes >类图:一种图解,用于展示类与类中的属性以及属性的值。 -##15.9 练习 -###练习1 +## 15.9 练习 +## # 练习1 写一个名为 Circle 的类的定义,属性为圆心center和半径radius,center 是一个点对象,半径是一个数值。 实例化一个 Circle 的对象,表示一个圆,圆心在(150,100),半径为75。 @@ -374,7 +374,7 @@ A diagram that shows objects, their attributes, and the values of the attributes [样例代码](http://thinkpython2.com/code/Circle.py)。 -###练习2 +## # 练习2 写一个名为 draw_rect的函数,接收一个 Turtle 对象和一个 Rectangle 对象作为参数,用 Turtle 画出这个矩形。可以参考一下第四章对 Turtle 对象使用的样例。 写一个名为 draw_circle 的函数,接收一个 Turtle 对象和一个 Circle 对象,画个圆这次。 diff --git a/chapter16.md b/chapter16.md index 4c4bef1..f94cb02 100644 --- a/chapter16.md +++ b/chapter16.md @@ -1,10 +1,10 @@ -#第十六章 类和函数 +# 第十六章 类和函数 现在我们已经知道如何创建新类型了,下一步就要写一些函数了,这些函数用自定义类型做参数和返回值。在本章中还提供了一种函数式编程的模式,以及两种新的程序开发规划方式。 本章的样例代码可以在[这里](http://thinkpython2.com/code/Time1.py)下载。然后练习题的样例代码可以在[这里](http://thinkpython2.com/code/Time1_soln.py)下载到。 -##16.1 时间 +## 16.1 时间 下面又是一个自定义类型的例子,这次咱们定义一个叫做 Time 的类,记录下当日的时间。 类的定义是如下这样: @@ -12,7 +12,7 @@ ```Python class Time: """Represents the time of day. -attributes: hour, minute, second """ +attributes: hour, minute, second """ ``` 我们可以建立一个新的 Time 对象,然后对时分秒分别进行赋值: @@ -32,10 +32,10 @@ time.second = 30 写一个布尔函数,名为 is_after,接收两个 Time 对象,分别为 t1和 t2,然后如果 t1在时间上位于 t2的后面,就返回真,否则返回假。难度提高一下:不要用 if 语句,看你能搞定不。 ________________________________________ -![Figure 16.1: Object diagram.](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPython16.1.png) +![Figure 16.1: Object diagram.](./images/figure16.1.jpg) Figure 16.1: Object diagram. ________________________________________ -##16.2 纯函数 +## 16.2 纯函数 后面的这些章节中,我们要写两个函数来对 time 进行加法操作。这两个函数展示了两种函数类型:纯函数和修改器。写这两个函数的过程中,也体现了我即将讲到的一种新的开发模式:原型和补丁模式,这种方法就是在处理复杂问题的时候,先从简单的原型开始,然后逐渐解决复杂的内容。 @@ -59,7 +59,7 @@ def add_time(t1, t2): >>> start = Time() >>> start.hour = 9 >>> start.minute = 45 ->>> start.second = 0 +>>> start.second = 0 >>> duration = Time() >>> duration.hour = 1 >>> duration.minute = 35 @@ -92,7 +92,7 @@ def add_time(t1, t2): 这回函数正确工作了,但代码也开始变多了。稍后我们就能看到一个短一些的替代方案。 -##16.3 修改器 +## 16.3 修改器 有时候需要对作为参数的对象进行一些修改。这时候这些修改就可以被调用者察觉。这样工作的函数就叫修改器了。 increment 函数,增加给定的秒数到一个 Time 对象,就可以被改写成一个修改器。 @@ -129,7 +129,7 @@ def increment(time, seconds): 做个练习,写一个用纯函数实现的 increment,创建并返回一个新的 Time 对象,而不是修改参数。 -##16.4 原型与规划 +## 16.4 原型与规划 这次我演示的开发规划就是『原型与补丁模式』。对每个函数,我都先谢了一个简单的原型,只进行基本的运算,然后测试一下,接下来逐步修补错误。 @@ -182,7 +182,7 @@ def add_time(t1, t2): 有意思的事,有时候以困难模式来写一个程序(比如用更加泛化的模式),反而能让开发更简单(因为这样就减少了特例情况,也减少了出错误的概率了。) -##16.5 调试 +## 16.5 调试 对于一个 Time 对象来说,只要分和秒的值在0-60的前闭后开区间(即可以为0但不可以为60),并且小时数为正数,就是格式正确的。小时和分钟都应该是整数,但秒是可以为小数的。 @@ -220,7 +220,7 @@ def add_time(t1, t2): assert 语句是很有用的,可以用来区分条件语句的用途,将 assert 这种用于检查错误的语句与常规的条件语句在代码上进行区分。 -##16.6 Glossary 术语列表 +## 16.6 Glossary 术语列表 prototype and patch: A development plan that involves writing a rough draft of a program, testing, and correcting errors as they are found. @@ -256,15 +256,15 @@ A statement that check a condition and raises an exception if it fails. >assert 语句:一种检查错误的语句,检查一个条件,如果不满足就抛出异常。 -##16.7 练习 +## 16.7 练习 本章的例子可以在 [这里](http://thinkpython2.com/code/Time1.py)下载;练习题的答案可以在[这里](http://thinkpython2.com/code/Time1_soln.py)下载。 -### 练习1 +## # 练习1 写一个函数,名为mul_time,接收一个Time 对象和一个数值,返回一个二者相乘得到的新的Time 对象。 然后用 mul_time 这个函数写一个函数,接收一个 Time 对象,代表着一个比赛的结束时间,还有一个数值,代表比赛距离,然后返回一个表示了平均步调(单位距离花费的时间)的新的 Time 对象。 -### 练习2 +## # 练习2 datetime 模块提供了一些 time 对象,和本章的 Time 对象很相似,但前者提供了更多的方法和运算符。读一读[这里的文档] [Here](http://docs.python.org/3/library/datetime.html)吧。 1. 用 datetime 模块来写一个函数,获取当前日期,然后输出今天是星期几。 diff --git a/chapter17.md b/chapter17.md index 916e9c5..b859d65 100644 --- a/chapter17.md +++ b/chapter17.md @@ -1,11 +1,11 @@ -#第十七章 类和方法 +# 第十七章 类和方法 前两章我们已经用到了Python 的一些面向对象的特性了,但那写程序实际上并不算是真正面向对象的,因为它们并没能够表现出用户自定义类型与对这些类型进行运算的函数之间的关系。所以接下来的移步就是要把这些函数转换成方法,让这些关系更明确。 本章的样例代码可以在 [这里下载](http://thinkpython2.com/code/Time2.py),然后练习题的样例代码可以在[这里下载](http://thinkpython2.com/code/Point2_soln.py)。 -##17.1 面向对象的特性 +## 17.1 面向对象的特性 Python 是一种面向对象的编程语言,这就意味着它提供了一些支持面向对象编程的功能,有以下这些特点: @@ -31,7 +31,7 @@ Python 是一种面向对象的编程语言,这就意味着它提供了一些 在接下来的章节中,我们就要把之前两章写过的一些函数改写成方法。这种转换是纯机械的;你就遵守一系列步骤就可以实现了。如果你对二者之间的相互转化很熟悉了,你就可以根据情况自己选择是用函数还是用方法。 -##17.2 输出对象 +## 17.2 输出对象 在16.1,我们定义过一个名为Time 的类,当时写过月名为 print_time 的函数: @@ -105,7 +105,7 @@ The reason for this convention is an implicit metaphor: 做个练习吧,重写一下 time_to_int(参见16.4),把这个函数写成一个方法。你也可以试着把 int_to_time 也携程方法,不过这可能不太行得通,因为把这个函数改成方法的话,没有对象来调用方法。 -##17.3 另外一个例子 +## 17.3 另外一个例子 下面是 increment 函数(参见16.4)被改写成的方法: @@ -148,7 +148,7 @@ sketch(parrot, cage, dead=True) parrot 和 cage 都是位置参数,dead 是关键字参数。 -##17.4 更复杂点的例子 +## 17.4 更复杂点的例子 重写 is_after(参见16.1),这就比较有难度了,因为这个函数接收两个 Time 对象作为参数。在这个情况下,一般就把第一个参数命名为 self,第二个命名为 other: ```Python @@ -164,7 +164,7 @@ True ``` 这里就体现出一种语法上的好处了,因为读起来基本就根英语是一样的嗯:『end is after start?』 -##17.5 init方法 +## 17.5 init方法 init 方法(就是对『initialization』的缩写,初始化的意思,这个方法相当于C++中的构造函数)是一种特殊的方法,在对象被实例化的时候被调用。这个方法的全名是__init__(两个下划线,然后是 init,然后还是两个下划线)。在 Time 类当中,init 方法示例如下: @@ -212,7 +212,7 @@ def __init__(self, hour=0, minute=0, second=0): 做一个练习,写一个 Point 类的 init 方法,接收 x 和 y 作为可选参数,然后赋值给对应的属性。 -##17.6 __str__方法 +## 17.6 __str__方法 __str__ 是一种特殊的方法,就跟__init__差不多,str 方法是接收一个对象,返回一个代表该对象的字符串。 @@ -233,7 +233,7 @@ def __str__(self): 写一个新的类的时候,总要先写出来 init 方法,这样有利于简化对象的初始化,还要写个 str 方法,这个方法在调试的时候很有用。 做个练习,写一下 Point 这个类的 str 方法。创建一个 Point 对象,然后用 print 输出一下。 -##17.7 运算符重载 +## 17.7 运算符重载 通过定义一些特定的方法,咱们就能针对自定义类型,让运算符有特定的作用。比如,如果你在 Time 类中定义了一个名字为__add__的方法,你就可以对 Time 对象使用『+』加号运算符。 @@ -253,11 +253,11 @@ def __add__(self, other): ``` 当你针对 Time 对象使用加号运算符的时候,Python 就会调用你刚刚自定义的 add 方法。当你用 print 输出结果的时候,Python 调用的是你自定义的 str 方法。所以实际上面这个简单的例子背后可不简单。 -针对用户自定义类型,让运算符有相应的行为,这就叫做运算符重载。Python 当中每一个运算符都有一个对应的方法,比如__add__。更多内容可以看一下 [这里的文档](http://docs.python.org/3/reference/datamodel.html#specialnames)。 +针对用户自定义类型,让运算符有相应的行为,这就叫做运算符重载。Python 当中每一个运算符都有一个对应的方法,比如__add__。更多内容可以看一下 [这里的文档](http://docs.python.org/3/reference/datamodel.html# specialnames)。 做个练习,给 Point 类写一个加法的方法。 -##17.8 根据对象类型进行运算 +## 17.8 根据对象类型进行运算 在前面的章节中,我们把两个 Time 对象进行了相加,但也许有时候需要把一个整数加到 Time 对象上面。下面这一个版本的__add__方法就能够实现检查类型,然后调用add_time 方法或者是 increment 方法: @@ -323,7 +323,7 @@ And here’s how it’s used: • 如果第二个运算数是一个元组,该方法就要把元组中第一个元素加到横坐标上,把第二个元素加到纵坐标上面,然后用计算出来的坐标返回一个新的点。 -##17.9 多态 +## 17.9 多态 在必要的时候,根据类型运算还是很有用的,不过(还好)并不总需要这么做。一般你都可以把函数写成能处理不同类型参数的,这样就不用这么麻烦了。 @@ -366,7 +366,7 @@ Time 对象有了自己的加法方法,就可以与 sum 函数来配合使用 最好就是无心插柳柳成荫的这种多态,这种情况下你会发现某个之前写过的函数可以用来处理一个之前没有计划到的类型。 -##17.10 调试 +## 17.10 调试 在程序运行的任意时刻都可以给对象增加属性,不过如果你有多个同类对象却又不具有相同的属性,就容易出错了。所以最好在对象实例化的时候就全部用 init 方法初始化对象的全部属性。 @@ -390,7 +390,7 @@ def print_attributes(obj): ``` 内置函数 getattr 会接收一个对象和一个属性名字(以字符串形式),然后返回该属性的值。 -##17.11 接口和实现 +## 17.11 接口和实现 面向对象编程设计的目的之一就是让软件更容易维护,这就意味着当系统中其他部分发生改变的时候依然能让程序运行,然后可以修改程序去符合新的需求。 @@ -412,7 +412,7 @@ def print_attributes(obj): 但如果你仔细地设计好接口,你在改变实现的时候就不用去折腾了,这就意味着你程序的其他部位都不需要改动了。 -##17.12 Glossary 术语列表 +## 17.12 Glossary 术语列表 object-oriented language: A language that provides features, such as programmer-defined types and methods, that facilitate object-oriented programming. @@ -458,12 +458,12 @@ The principle that the interface provided by an object should not depend on its >信息隐藏:一种开发原则,一个对象提供的接口应该独立于其实现,尤其是不受对象属性设置变化的影响。 -##17.13 练习 -###练习1 +## 17.13 练习 +## # 练习1 从[这里](http://thinkpython2.com/code/Time2.py)下载本章的代码。把 Time 中的属性改变成一个单独的整型变量,用来表示自从午夜至今的秒数。然后修改一下各个方法(以及 int_to_time 函数),让所有功能都能在新的实现下正常工作。尽量就让自己不用去更改main 当中的测试代码。你改完之后,输出应该与之前相同。[样例代码](http://thinkpython2.com/code/Time2_soln.py) -###练习2 +## # 练习2 这个练习是一个广为流传的寓言故事,其中包含了一个使用 Python的时候最常见但也是最难发现的错误。写一个名为袋鼠的类的定义,要求有如下的方法: diff --git a/chapter18.md b/chapter18.md index 1b6d23a..88440a1 100644 --- a/chapter18.md +++ b/chapter18.md @@ -1,4 +1,4 @@ -#第十八章 继承 +# 第十八章 继承 面向对象编程最常被人提到的语言功能就是继承了。继承就是基于一个已有的类进行修改来定义一个新的类。在本章我会用一些例子来演示继承,这些例子会用到一些类来表示扑克牌,成副的纸牌和扑克牌型。 @@ -6,7 +6,7 @@ 本章的代码样例可以在[这里](http://thinkpython2.com/code/Card.py)下载。 -##18.1 纸牌对象 +## 18.1 纸牌对象 牌桌上面一共有52张扑克牌,每一张都属于四种花色之一,并且是十三张牌之一。花色为黑桃,红心,方块,梅花(在桥牌中按照降序排列)。排列顺序为 A,2,3,4,5,6,7,8,9,10,J,Q,K。根据具体玩的游戏的不同,A 可以比 K 大,也可以比2还小。 @@ -50,7 +50,7 @@ class Card: ```Python queen_of_diamonds = Card(1, 12) ``` -##18.2 类的属性 +## 18.2 类的属性 想要以易于被人理解的方式来用 print 打印输出纸牌对象,我们就得建立一个从整形编码到对应的牌值和花色的映射。最自然的方法莫过于用字符串列表来实现。咱们可以先把这些列表赋值到类的属性中去: @@ -88,13 +88,13 @@ rank_names 的一个元素是None空,因为没有牌值为0的纸牌。包含 Jack of Hearts ``` ________________________________________ -![Figure 18.1: Object diagram.](http://7xnq2o.com1.z0.glb.clouddn.com/18.1.png) +![Figure 18.1: Object diagram.](./images/figure18.1.jpg) Figure 18.1: Object diagram. ________________________________________ 图18.1是一个 Card 类对象以及一个 Card 实例的图解。Card 是一个类对象(就是类的一个实例);它的类型是type。card1是 Card 的一个实例,所以它的类型是 Card。为了节省空间,我没有画出 suit_names 和 rank_names 的内容。 -##18.3 对比牌值 +## 18.3 对比牌值 对于内置类型,直接就可以用关系运算符(<,>, ==,等等)比较两个值来判断二者的大小以及是否相等。对与用户自定义类型,咱们就要覆盖掉内置运算符的行为,这就需要提供一个名为__lt__的方法,这个lt 就是『less than』的缩写,意思是『小于』。 @@ -130,7 +130,7 @@ def __lt__(self, other): 做个练习,为 Time 对象写一个__lt__方法。可以用元组对比,不过也可以对比整数。 -##18.4 Decks 成副的纸牌 +## 18.4 Decks 成副的纸牌 现在咱们已经有了纸牌的类了,接下来的一不就是定义成副纸牌了。因为一副纸牌上是有各种牌,所以很自然就应该包含一个纸牌列表作为一个属性了。 @@ -149,12 +149,12 @@ class Deck: 实现一副牌的最简单方法就是用网状循环了。外层循环枚举花色从0到3一共四种。内层的循环枚举从1到13的所有牌值。每一次循环都以当前的花色和牌值创建一个新的Card 对象,添加到 self.cards 列表中。 -##18.5 输出整副纸牌 +## 18.5 输出整副纸牌 下面是 Deck 类的__str__方法: ```Python -#inside class Deck: +# inside class Deck: def __str__(self): res = [] for card in self.cards: @@ -184,12 +184,12 @@ King of Spades 虽然结果看上去是52行,但实际上只是一个包含了很多换行符的一个长字符串。 -##18.6 添加,删除,洗牌和排序 +## 18.6 添加,删除,洗牌和排序 要处理纸牌,我们还需要一个方法来从牌堆中拿出和放入纸牌。列表的 pop 方法很适合来完成这件任务: ```Python -#inside class Deck: +# inside class Deck: def pop_card(self): return self.cards.pop() ``` @@ -197,7 +197,7 @@ pop 方法从列表中拿走最后一张牌,这样就是从一副牌的末尾 要添加一张牌,可以用列表的 append 方法: ```Python -#inside class Deck: +# inside class Deck: def add_card(self, card): self.cards.append(card) ``` @@ -219,7 +219,7 @@ def shuffle(self): 做个练习吧,写一个名为 sort 的方法给 Deck,使用列表的sort 方法来给 Deck 中的牌进行排序。sort 方法要用到我们之前写过的 __lt__ 方法来确定顺序。 -##18.7 继承 +## 18.7 继承 I 继承就是基于已有的类进行修改来获取新类的能力。举个例子,比方说我们需要一个表示『一手牌』的类,这个就是指一个牌手手中拿着的牌。『一手牌』和『一副牌』有些相似:都是由一系列的纸牌组成的,也都要有添加和移除纸牌的运算。 @@ -267,7 +267,7 @@ King of Spades 接下来很自然地,我们把这段名为 move_cards 的方法放进去: ```Python -#inside class Deck: +# inside class Deck: def move_cards(self, hand, num): for i in range(num): hand.add_card(self.pop_card()) @@ -282,7 +282,7 @@ move_cards 方法接收两个参数,一个 Hand 对象,以及一个要处理 然而继承也容易降低程序可读性。当调用一个方法的时候,有时候不容易找到该方法的定义位置。相关的代码可能跨了好几个模块。此外,很多事情可以用继承来实现,但不用继承也能做到同样效果,甚至做得更好。 -##18.8 类图 +## 18.8 类图 目前为止,我们见过栈图了,栈图是展示一个程序的状态的,我们还见过对象图了,表示的是一个对象中的各个属性及其值。这些图都是对一个程序运行中某个瞬间的反映,因此随着程序运行而产生变化。 @@ -300,7 +300,7 @@ move_cards 方法接收两个参数,一个 Hand 对象,以及一个要处理 类图就是对这些关系的一个图形化的表示。比如,在途18.2中,就展示了 Card,Deck 以及 Hand 三个类的关系。 ________________________________________ -![Figure 18.2: Class diagram.](http://7xnq2o.com1.z0.glb.clouddn.com/18.2.png) +![Figure 18.2: Class diagram.](./images/figure18.2.jpg) Figure 18.2: Class diagram. ________________________________________ @@ -318,7 +318,7 @@ ________________________________________ 更细节化的图解就可能表现出一个 Deck 中会包含一个 Card 对象组成的列表,但一般情况下类图不会包括内置类型比如列表和字典。 -##18.9 调试 +## 18.9 调试 继承可以让调试变得很夸你呢,因为你调用某个对象中的某个方法的时候,很难确定到底是调用的哪一个方法。 @@ -357,7 +357,7 @@ H 如果你违背了上面这个『里氏替换原则』,你的代码就可能很悲剧地崩溃,就像无数纸牌坍塌一样。 -##18.10 数据封装 +## 18.10 数据封装 之前的章节中,我们展示了所谓『面向对象设计』的开发规划模式。在这些章节中,我们显示确定好需要的对象—比如点,矩形以及时间—然后再定义一些类去代表这些内容。在这些例子中,类的对象与现实世界(或者至少是数学世界)中的一些实体都有显著的对应关系。 @@ -394,7 +394,7 @@ def process_word(self, word, order=2): try: self.suffix_map[self.prefix].append(word) except - KeyError: # if there is no entry for this prefix, make one + KeyError: # if there is no entry for this prefix, make one self.suffix_map[self.prefix] = [word] self.prefix = shift(self.prefix, word) ``` @@ -413,7 +413,7 @@ def process_word(self, word, order=2): 做一个练习,从[这里](http://thinkpython2.com/code/markov.py)下载我的马科夫分析代码,然后根据上面说的步骤来一步步把全局变量封装成一个名为 Markov 的新类的属性。[样例代码](http://thinkpython2.com/code/Markov.py) (一定要注意 M 是大写的哈) -##18.11 Glossary 术语列表 +## 18.11 Glossary 术语列表 encode: To represent one set of values using another set of values by constructing a mapping between them. @@ -479,14 +479,14 @@ A program development plan that involves a prototype using global variables and >数据封装:一种程序开发规划方式,用全局变量做原型体,然后逐步将这些全局变量转换成实例的属性。 -##18.12 练习 -###练习1 +## 18.12 练习 +## # 练习1 阅读下面的代码,画一个 UML 类图,表示出程序中的类,以及类之间的关系。 ```Python class PingPongParent: - pass class Ping(PingPongParent): + pass class Ping(PingPongParent): def __init__(self, pong): self.pong = pong class Pong(PingPongParent): @@ -501,11 +501,11 @@ class Pong(PingPongParent): ping = Ping(pong) pong.add_ping(ping) ``` -###练习2 +## # 练习2 为 Deck 类写一个名为 deal_hands 的方法,接收两个参数,一个为牌型数量,一个为每一个牌型的纸牌数。该方法需要创建适当的牌型对象数量,处理适当的每个牌型中的纸牌数,然后返回一个牌型组成的列表。 -###练习3 +## # 练习3 下面是扑克牌中可能的各个牌型,排列顺序为值的升序,出现概率的降序: diff --git a/chapter19.md b/chapter19.md index a27a260..c4c1f50 100644 --- a/chapter19.md +++ b/chapter19.md @@ -1,9 +1,9 @@ -#第十九章 更多功能 +# 第十九章 更多功能 我在本书中的一个目标就是尽量少教你 Python(译者注:而要多教编程)。有的时候完成一个目的有两种方法,我都会只选择一种而不提其他的。或者有的时候我就把第二个方法放到练习里面。 现在我就要往回倒车一下,捡起一些当时略过的重要内容来给大家讲一下。Python 提供了很多并非必须的功能—你完全可以不用这些功能也能写出很好的代码—但用这些功能有时候能让你的代码更加简洁,可读性更强,或者更有效率,甚至有时候能兼顾这三个方面。 -##19.1 条件表达式 +## 19.1 条件表达式 在5.4中,我们见到了条件语句。条件语句往往用于二选一的情况下;比如: @@ -40,7 +40,7 @@ def factorial(n): ```Python def factorial(n): - return 1 if n == 0 else return n * factorial(n-1) + return 1 if n == 0 else return n * factorial(n-1) ``` 条件表达式还可以用于处理可选参数。例如下面就是练习2中 GoodKangaroo 类的 init 方法: @@ -64,7 +64,7 @@ def __init__(self, name, contents=None): 一般来讲,你可以用条件表达式来替换掉条件语句,无论这些语句的分支是返回语句或者是赋值语句。 -##19.2 列表推导 +## 19.2 列表推导 在10.7当中,我们看到了映射和过滤模式。例如,下面这个函数接收一个字符串列表,然后将每一个元素都用字符串方法 capitalize 处理成大写的,然后返回一个新的字符串列表: @@ -112,7 +112,7 @@ def only_upper(t): 但是,我也要辩护一下,列表推导会导致调试非常困难,因为你不能在循环内部放 print 语句了。我建议你只去在一些简单的地方使用,要确保你第一次写出来就能保证代码正常工作。也就是说初学者就还是别用为好。 -##19.3 生成器表达式 +## 19.3 生成器表达式 生成器表达式与列表推导相似,用的不是方括号,而是圆括号: @@ -154,7 +154,7 @@ StopIteration >>> sum(x**2 for x in range(5)) 30 ``` -##19.4 any和all +## 19.4 any和all Python 提供了一个名为 any 的内置函数,该函数接收一个布尔值序列,只要里面有任意一个是真,就返回真。该函数适用于列表: @@ -188,7 +188,7 @@ Python 还提供了另外一个内置函数 all,该函数在整个序列都是 做个练习,用 all 来改写一下9.3中的uses_all 函数。 -##19.5 集合 +## 19.5 集合 在13.6中,我用了字典来查找存在于文档中而不存在于词汇列表中的词汇。我写的这个函数接收两个参数,一个是 d1是包含了文档中的词作为键,另外一个是 d2包含了词汇列表。程序会返回一个字典,这个字典包含的键存在于 d1而不在 d2中。 @@ -259,7 +259,7 @@ def uses_only(word, available): ``` 这里的<=运算符会检查一个集合是否切另外一个集合的子集或者相等,如果 word 中所有的字符都出现在 available 中就返回真。 -##19.6 计数器 +## 19.6 计数器 计数器跟集合相似,除了一点,就是如果计数器中元素出现的次数超过一次,计数器会记录下出现的次数。如果你对数学上多重集的概念有所了解,就会知道计数器是一种对多重集的表示方式。 @@ -298,7 +298,7 @@ r 2 p 1 a 1 ``` -##19.7 默认字典 +## 19.7 默认字典 collection 模块还提供了一个默认字典,与普通字典的区别在于当你读取一个不存在的键的时候,程序会添加上一个新值给这个键。 @@ -372,7 +372,7 @@ def all_anagrams(filename): 你可以从[这里](http://thinkpython2.com/code/PokerHandSoln.py)下载我给练习3写的样例代码,该代码中在 has_straightflush函数用的是默认集合。这份代码的不足就在于每次循环都要创建一个 Hand 对象,而不论是否必要。做个练习,用默认字典来该写一下这个程序。 -##19.8 命名元组 +## 19.8 命名元组 很多简单的类就是一些相关值的集合。例如在15章中定义的 Point 类中就包含两个数值,x 和 y。当你这样定义一个类的时候,你通常要写一个 init 方法和一个 str 方法: @@ -438,7 +438,7 @@ Or you could switch to a conventional class definition.>或者你可以把命名元组转换成一个常规的类的定义。 -##19.9 收集关键词参数 +## 19.9 收集关键词参数 在12.4中,我们已经学过了如何写将参数收集到一个元组中的函数: @@ -487,12 +487,12 @@ Point(x=1, y=2) ```Python >>> d = dict(x=1, y=2) >>> Point(d) -Traceback (most recent call last): File "", line 1, in TypeError: __new__() missing 1 required positional argument: 'y' +Traceback (most recent call last): File "", line 1, in TypeError: __new__() missing 1 required positional argument: 'y' ``` 当你写一些有大量参数的函数的时候,就可以创建和使用一些字典,这样能把各种常用选项弄清。 -##19.10 Glossary 术语列表 +## 19.10 Glossary 术语列表 conditional expression: An expression that has one of two values, depending on a condition. @@ -518,8 +518,8 @@ A function, usually passed as a parameter, used to create objects. >工厂:一个函数,通常作为参数传递,用来产生对象。 -##19.11 练习 -###练习1 +## 19.11 练习 +## # 练习1 下面的函数是递归地计算二项式系数的。 @@ -528,7 +528,7 @@ def binomial_coeff(n, k): """Compute the binomial coefficient "n choose k". n: number of trials k: number of successes - returns: int """ + returns: int """ if k == 0: return 1 if n == 0: diff --git a/chapter2.md b/chapter2.md index 6d189cc..b85324a 100644 --- a/chapter2.md +++ b/chapter2.md @@ -1,8 +1,8 @@ -#第二章 变量,表达式,语句 +# 第二章 变量,表达式,语句 编程语言最强大的功能就是操作变量。变量就是一个有值的代号。 -##2.1 赋值语句 +## 2.1 赋值语句 赋值语句的作用是创建一个新的变量,并且赋值给这个变量: @@ -18,10 +18,10 @@ 平常大家在纸上对变量赋值的方法就是写下名字,然后一个箭头指向它的值。这种图解叫做状态图,因为它能指明各个变量存储的是什么内容。下图就展示了上面例子中赋值语句的结果。 ________________________________________ - ![Figure 2.1: State diagram.](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPython2.1.png) + ![Figure 2.1: State diagram.](./images/figure2.1.jpg) Figure 2.1: State diagram. ________________________________________ -##2.2 变量名称 +## 2.2 变量名称 编程的人总得给变量起个有一定意义的名字才能记得住,一般情况就用名字来表示这个变量的用途了。 @@ -37,7 +37,7 @@ ________________________________________ SyntaxError: invalid syntax >>> more@ = 1000000 SyntaxError: invalid syntax ->>> class = 'Advanced Theoretical Zymurgy' +>>> class = 'Advanced Theoretical Zymurgy' SyntaxError: invalid syntax ``` 第一个数字开头所以不合规则,第二个有非法字符@,第三个这个class咋不行呢?好奇吧? @@ -47,17 +47,17 @@ SyntaxError: invalid syntax 以下是Python3的关键词哈: -* False class finally is -* return None continue for lambda -* try True def from nonlocal -* while and del global not -* with as elif if or -* yield assert else import pass -* break except in raise +* False class finally is +* return None continue for lambda +* try True def from nonlocal +* while and del global not +* with as elif if or +* yield assert else import pass +* break except in raise 你不用去记忆这些哈。因为一般大多数的开发环境里面,关键词都会有区别于普通代码的颜色提示你,你要是用他们做变量名了,一看就会知道的。 -##2.3 表达式和语句 +## 2.3 表达式和语句 表达式是数值,变量和操作符的组合。单个值本身也被当作一个表达式,变量也是如此,下面这些例子都是一些正确表达式: @@ -85,7 +85,7 @@ SyntaxError: invalid syntax 输入语句的时候,解释器会执行它,就是会按照语句所说的去做。一般语句是没有值的。 -##2.4 脚本模式 +## 2.4 脚本模式 以上我们一直在用Python的交互模式,就是直接咱们人跟解释器来交互。开始学的时候这样挺好的,但如果你要想一次运行多行代码,这样就很不方便了。 @@ -144,7 +144,7 @@ produces the output ``` 现在再把同样的语句输入到脚本中,然后用Python来运行一下。看看输出是啥样的?把脚本中的表达式修改一下,每一个都加一个打印语句再试试。 -##2.5 运算符优先级 +## 2.5 运算符优先级 表达式可能会包含不止一个运算符,这些不同的运算先后次序就是运算符的优先级。对于数学运算符来说,Python就遵循着数学上的规则。下面这个PEMDAS、是用来记忆这些优先规则的好方法: @@ -158,7 +158,7 @@ produces the output 我不会花很大力气来记忆这些运算符的优先级。如果我怕记不住弄错了,就用括号来让优先级明确一下就好。 -##2.6 字符串操作 +## 2.6 字符串操作 一般情况下,咱们不能对字符串进行数学运算的,即使字符串看上去像是数字也不行,所以以下这些都是非法操作: @@ -174,7 +174,7 @@ produces the output +加号的意思就是字符串拼接了,会把两个字符串拼到一起,如下所示: ```Python ->>> first = 'throat' +>>> first = 'throat' >>> second = 'warbler' >>> first + second throatwarbler @@ -186,11 +186,11 @@ throatwarbler 这种加法和乘法实际上就是拼接和重复的意思。 -##2.7 注释 +## 2.7 注释 程序会越来越庞大,也越复杂了,读起来就会更难了。公式语言很密集,靠阅读来理解代码,总是很困难的。 -为了解决阅读的困难,咱们就可以添加一些笔记到代码中,把程序的功能用自然语言来解释一下。这种笔记就叫注释了,使用井号#来开头的: +为了解决阅读的困难,咱们就可以添加一些笔记到代码中,把程序的功能用自然语言来解释一下。这种笔记就叫注释了,使用井号# 来开头的: ```Python # compute the percentage of the hour that has elapsed percentage = (minute * 100) / 60 @@ -199,10 +199,10 @@ throatwarbler 注释可以另起一行,也可以放到行末尾: ```Python -percentage = (minute * 100) / 60 # percentage of an hour +percentage = (minute * 100) / 60 # percentage of an hour ``` -井号#后面的内容都会被忽略,因此不会影响程序的运行结果。 +井号# 后面的内容都会被忽略,因此不会影响程序的运行结果。 一般注释都是用来解释代码的一些不明显的特性。一般情况下读代码的人应该能理解代码的功能是什么,所以用注释多是要解释这样做的目的是什么。 @@ -211,22 +211,22 @@ percentage = (minute * 100) / 60 # percentage of an hour 下面这个注释就显然是多余的,根本没必要: ```Python -v = 5 # assign 5 to v +v = 5 # assign 5 to v ``` 下面这种注释包含了重要信息,就很重要了: ```python -v = 5 # velocity in meters/second. +v = 5 # velocity in meters/second. ``` 变量命名得当的话,就没必要用太多注释了,不过名字要是太长了,表达式读起来也挺麻烦,所以就得权衡着来了。 -##2.8 调试 +## 2.8 调试 程序一般会有三种错误:语法错误,运行错误和语义错误。区分这三种错误有助于更快速地追踪错误。 -* 语法错误Syntax error: +* 语法错误Syntax error: 语法是指程序的结构和规则。比如括号要成对用。如果你的程序有某个地方出现了语法错误,Python会显示出错信息并退出,程序就不能运行了。最开始学习编程的这段时间,你遇到的最常见的估计就是这种情况。等你经验多了,基本就犯的少了,而且也很容易发现了。 @@ -238,7 +238,7 @@ v = 5 # velocity in meters/second. 第三种就是语义错误,顾名思义,是跟意义相关。这种错误是指你的程序运行没问题,也不产生错误信息,但不能正确工作。程序可能做一些和设计目的不同的事情。发现语义错误特别不容易,需要你仔细回顾代码和程序输出,要搞清楚到底程序做了什么。 -##2.9 Glossary 术语列表 +## 2.9 Glossary 术语列表 variable: A name that refers to a value. 变量:有值的量。 @@ -334,8 +334,8 @@ An error in a program that makes it do something other than what the programmer >语义错误:程序运行的结果和料想的不一样,没有完成设计的功能,而是干了点其他的事情。 -##2.10 练习 -###练习1 +## 2.10 练习 +## # 练习1 像上一章一样,按我建议的,不论学了什么新内容,你都试着在交互模式上故意犯点错误,看看会怎么样。 @@ -349,7 +349,7 @@ An error in a program that makes it do something other than what the programmer * 数学上你可以把x和y相乘写成xy,Python里面你这么试试看? -###练习2 +## # 练习2 把Python解释器当做计算器来做下面的练习: diff --git a/chapter3.md b/chapter3.md index 2ef2049..378046d 100644 --- a/chapter3.md +++ b/chapter3.md @@ -1,8 +1,8 @@ -#第三章 函数 +# 第三章 函数 在编程的语境下,"函数"这个词的意思是对一系列语句的组合,这些语句共同完成一种运算。定义函数的时候,你要给这个函数指定一个名字,另外还好写出这些进行运算的语句。定义完成后,就可以通过函数名来"调用"函数。 -##3.1 函数调用 +## 3.1 函数调用 此前我们已经见识过函数调用的一个例子了: @@ -16,7 +16,7 @@ 一般来说,函数都要"传入"一个参数,"返回"一个结果。结果也被叫做返回值。Python提供了一些转换数值类型的函数。比如int这个函数就可以把值转换成整形,但不是什么都能转的,遇到不能转换的就会报错了,如下所示: ```Python ->>> int('32') +>>> int('32') 32 >>> int('Hello') ValueError: invalid literal for int(): Hello @@ -25,7 +25,7 @@ ValueError: invalid literal for int(): Hello int这个函数能把浮点数转成整形,但不是很完美,小数部分就都给砍掉了。 ```Python ->>> int(3.99999) +>>> int(3.99999) 3 >>> int(-2.3) -2 @@ -43,12 +43,12 @@ float能把整形和字符串转变成浮点数: 最后来看下,str可以把参数转变成字符串: ```Python ->>> str(32) -'32' +>>> str(32) +'32' >>> str(3.14159) '3.14159' ``` -##3.2 数学函数 +## 3.2 数学函数 Python内置了一个数学模块,这一模块提供了绝大部分常用的数学函数。模块就是一系列相关函数的集合成的文件。 @@ -61,7 +61,7 @@ Python内置了一个数学模块,这一模块提供了绝大部分常用的 这个语句建立了一个模块对象,名字叫做math。如果你让这个模块对象显示一下,你就会得到与之相关的信息了: ```Python ->>> math +>>> math ``` @@ -69,7 +69,7 @@ Python内置了一个数学模块,这一模块提供了绝大部分常用的 ```Python >>> ratio = signal_power / noise_power ->>> decibels = 10 * math.log10(ratio) +>>> decibels = 10 * math.log10(ratio) >>> radians = 0.7 >>> height = math.sin(radians) ``` @@ -97,7 +97,7 @@ math.pi这个表达式从数学模块中得到π的一个大概精确到15位的 >译者注:画一个三角形就知道了,45度角两直角边是单位1,斜边必然是2的平方根了,对应的正弦余弦也都是这个值了。大家应该能理解吧? -##3.3 组合 +## 3.3 组合 目前为止,我们已经见识了一个程序所需要的大部分元素了:变量、表达式、语句。不过咱们都是一个个单独看到的,还没有把它们结合起来试试。 @@ -115,21 +115,21 @@ x = math.exp(math.log(x+1)) 你可以在任何地方放一个值,放任何一个表达式,只有一个例外:一个声明语句的左边必须是变量名。任何其他的表达式放到等号左边都会导致语法错误(当然也有例外,等会再给介绍)。 ```Python ->>> minutes = hours * 60 # right ->>> hours * 60 = minutes # wrong! +>>> minutes = hours * 60 # right +>>> hours * 60 = minutes # wrong! SyntaxError: can't assign to operator ``` >译者注:上述例子里面把表达式复制为变量是不行的,所说的例外估计是指尤达大师命名法,这个后面看到再说。 -##3.4 自定义函数 +## 3.4 自定义函数 目前我们学到了一些Python自带的函数,自己定义新的函数也是可以的。函数定义要指定这个新函数的名字,还需要一系列语句放到这个函数里面,当调用这个函数的时候,就会运行这些语句了。 ```Python def print_lyrics(): - print("I'm a lumberjack, and I'm okay.") - print("I sleep all night and I work all day.") + print("I'm a lumberjack, and I'm okay.") + print("I sleep all night and I work all day.") ``` 这里的def就是一个关键词,意思是这是在定义一个函数。函数的名字就是print_lyrics,函数的命名规则和变量命名规则基本差不多,都是字幕梳子或者下划线,但是不能用数字打头。另外也不能用关键词做函数名,还要注意尽量避免函数名和变量名发生重复。 @@ -152,8 +152,8 @@ def print_lyrics(): ```Python >>> def print_lyrics(): -... -print("I'm a lumberjack, and I'm okay.") ... +... +print("I'm a lumberjack, and I'm okay.") ... print("I sleep all night and I work all day.") ... ``` 在函数定义完毕的结尾,必须输入一行空白行。定义函数会创建一个函数类的对象,有type函数。 @@ -177,7 +177,7 @@ I'm a lumberjack, and I'm okay. I sleep all night and I work all day. ```Python def repeat_lyrics(): print_lyrics() - + ``` 然后调用一下这个函数: @@ -189,16 +189,16 @@ I'm a lumberjack, and I'm okay. I sleep all night and I work all day. I'm a lumb 当然了,实际这首歌可不是这样的哈。 -##3.5 定义并使用 +## 3.5 定义并使用 把前面这些小块的代码来整合一下,整体上程序看着大概是这样的: ```Python -def print_lyrics(): - print("I'm a lumberjack, and I'm okay.") - print("I sleep all night and I work all day.") -def repeat_lyrics(): - print_lyrics() +def print_lyrics(): + print("I'm a lumberjack, and I'm okay.") + print("I sleep all night and I work all day.") +def repeat_lyrics(): + print_lyrics() repeat_lyrics() ``` @@ -213,7 +213,7 @@ repeat_lyrics() 然后再把函数调用放到底部,把print_lyrics这个函数的定义放到repeat_lyrics这个函数的后面。再看看这次运行会出现什么样子? -##3.6 运行流程 +## 3.6 运行流程 为了确保一个函数在首次被调用之前已经定义,你必须要之道语句运行的顺序,也就是所谓『运行流程』。 @@ -235,7 +235,7 @@ repeat_lyrics() 总的来说,你阅读一个程序的时候,并不一定总是要从头到尾来读的。有时候你要按照运行流程来读才更好理解。 -##3.7 形式参数和实际参数 +## 3.7 形式参数和实际参数 (译者注:这里提到的形参和实参实际上是传值方式的区别,这个在最基本的编程入门课程中老师应该都比较强调的。实际参数就是调用函数时候传给他的那个参数;而形式参数可以理解为函数内部定义用的参数。老外对这个的思辩也很多。这里我先不说太多,翻译着再看。 大家可以去网上多搜索一下,比如在[StackOverflow](http://stackoverflow.com/questions/1788923/parameter-vs-argument)和[MSDN](https://msdn.microsoft.com/en-us/library/9kewt1b3.aspx)) @@ -246,15 +246,15 @@ repeat_lyrics() ```Python def print_twice(bruce): - print(bruce) + print(bruce) print(bruce) - + ``` 这个函数把传来的实际参数的值赋给了一个名字叫做burce的形式参数。当函数被调用的时候,就会打印出形式参数的值两次(无论是什么内容)。任何能打印的值都适用于这个函数。 ```Python ->>> print_twice('Spam') +>>> print_twice('Spam') Spam Spam >>> print_twice(42) @@ -290,15 +290,15 @@ Eric, the half a bee. (译者注:这里要跟大家解释一下,传递参数的时候用的是实际参数,是把这个实际参数的值交给调用的函数,函数内部接收这个值,可以命名成任意其他名字的形式参数,差不多就这么个意思了。) -##3.8 函数内部变量和形参都是局部的 +## 3.8 函数内部变量和形参都是局部的 在函数内部建立一个变量,这个变量是仅在函数体内部才存在。例如: ```Python -def cat_twice(part1, part2): - cat = part1 + part2 +def cat_twice(part1, part2): + cat = part1 + part2 print_twice(cat) - + ``` 这个函数得到两个实参,把它们连接起来,然后调用print_twice函数来输出结果两次。 @@ -322,14 +322,14 @@ NameError: name 'cat' is not defined (译者注:当然你可以在函数外定义一个同名变量叫做bruce,但这两个没有关系,大家可以动手自己试试,这也是作者所鼓励的一种探索思维。) -##3.9 栈图 +## 3.9 栈图 要追踪一个变量能在哪些位置使用,咱们就可以画个图表来实现,这种图表叫做栈图。栈图和我们之前提到的状态图有些相似,也会表征每个变量的值,不同的是栈图还会标识出每个变量所属的函数。 每个函数都用一个框架来表示。框架的边上要标明函数的名字,框内填写函数内部的形参和变量。上文中样例代码的栈图如下图3.1所示。 -![Figure 3.1: Stack diagram.](http://www.greenteapress.com/thinkpython2/html/thinkpython2002.png) +![Figure 3.1: Stack diagram.](./images/figure3.1.jpg) 图3.1 栈图 @@ -345,12 +345,12 @@ NameError: name 'cat' is not defined 例如,如果你想在print_twice这个函数中读取cat的值,就会得到一个变量名错误: ```Python -Traceback (innermost last): -File "test.py", line 13, in __main__ -cat_twice(line1, line2) -File "test.py", line 5, in cat_twice -print_twice(cat) -File "test.py", line 9, in print_twice +Traceback (innermost last): +File "test.py", line 13, in __main__ +cat_twice(line1, line2) +File "test.py", line 5, in cat_twice +print_twice(cat) +File "test.py", line 9, in print_twice print(cat) NameError: name 'cat' is not defined ``` @@ -360,7 +360,7 @@ NameError: name 'cat' is not defined 追溯中对函数顺序的排列是同栈图的方框顺序一样的。当前运行的函数会放在最底部。 -##3.10 有返回值的函数 和 无返回值的函数 +## 3.10 有返回值的函数 和 无返回值的函数 咱们用过的一些函数,比如数学的函数,都会返回各种结果;也没别的好名字,就叫他们有返回值函数。其他的函数,比如print_twice,都是进行一些操作,但不返回值。那就叫做无返回值函数好了。 @@ -393,7 +393,7 @@ math.sqrt(5) 无返回值的函数要么就是屏幕上显示出一些内容,要么就有其他的功能,但就是没有返回值。如果你把这种函数的结果返回给一个变量,就会的到特殊的值:空。 ```Python ->>> result = print_twice('Bing') +>>> result = print_twice('Bing') Bing Bing >>> print(result) None @@ -407,7 +407,7 @@ None ``` 我们目前为止写的函数还都是无返回值的。接下来的新的章节里面,咱们就要开始写一些有返回值的函数了。 -##3.11 为啥要用函数? +## 3.11 为啥要用函数? 为什么要费这么多力气来把程序划分成一个个函数呢?这么麻烦值得么?原因如下: @@ -419,7 +419,7 @@ None * 精细设计的函数会对很多程序都有用处。一旦你写好了并且除了错,这种函数代码可以再利用。 -##3.12 调试 +## 3.12 调试 给程序调试是你应当掌握的最关键技能之一了。尽管调试的过程会有挫败感,也依然是最满足智力,最有挑战性,也是编程过程中最有趣的一个项目了。 @@ -433,7 +433,7 @@ None 例如,Linux是一个有上百万行代码的操作系统,但最早它起源于Linus Torvalsd的一段小代码。这个小程序是作者用来探索Intel的80386芯片的。根据Larry Greenfield回忆,『Linus早起的项目就是很小的一个程序,这个程序能够在输出AAAA和BBBB之间进行转换。这后来就发展除了Linux了。』(引用自Linux用户参考手册beta1版) -##3.13 Glossary 术语列表 +## 3.13 Glossary 术语列表 function: A named sequence of statements that performs some useful operation. Functions may or may not take arguments and may or may not produce a result. @@ -544,32 +544,32 @@ A list of the functions that are executing, printed when an exception occurs. >追踪:对运行中函数的列表,当有异常的时候就会输出。 -##3.14 练习 -### 练习1 +## 3.14 练习 +## # 练习1 写一个名叫right_justify的函数,形式参数是名为s的字符串,将字符串打印,前面流出足够的空格,让字符串最后一个字幕在第70列显示。 ```Python ->>> right_justify('monty') monty +>>> right_justify('monty') monty ``` 提示:使用字符拼接和重复来实现。另外Python还提供了内置的名字叫做len的函数,可以返回一个字符串的长度,比如len('monty')的值就是5了。 -### 练习2 +## # 练习2 你可以把一个函数对象作为一个值赋给一个变量或者作为一个实际参数来传递给其他函数。比如,do_twice就是一个把其他函数对象当做参数的函数,它的功能是调用对象函数两次: ```Python -def do_twice(f): - f() - f() +def do_twice(f): + f() + f() ``` 下面是另一个例子,这里用了do_twice来调用一个名叫print_spam的函数两次。 ```Python -def print_spam(): -print('spam') +def print_spam(): +print('spam') do_twice(print_spam) ``` 1.把上面的例子写成脚本然后试一下。 @@ -584,7 +584,7 @@ do_twice(print_spam) [样例代码](http://thinkpython2.com/code/do_four.py): -###3 练习三 +## # 3 练习三 注意:这个练习应该只用咱们目前学习过的语句和其他功能来实现。 diff --git a/chapter4.md b/chapter4.md index c94240b..e1d8a66 100644 --- a/chapter4.md +++ b/chapter4.md @@ -1,4 +1,4 @@ -#第四章 案例学习:交互设计 +# 第四章 案例学习:交互设计 本章会提供一个案例,用于展示如何却设计一些共同工作的函数。 @@ -11,7 +11,7 @@ 本章代码样例可以点击[此链接](http://thinkpython2.com/code/polygon.py)来下载了。 -##4.1 乌龟模块 +## 4.1 乌龟模块 要检查你是不是已经安装了这个乌龟模块,你要打开Python解释器来输入如下内容: @@ -68,24 +68,24 @@ bob.fd(100) 现在修改一下程序,去画一个正方形。这个程序运行不好的话就不要继续后面的章节! -##4.2 简单的重复 +## 4.2 简单的重复 你估计会写出如下的内容: ```Python bob.fd(100) -bob.lt(90) +bob.lt(90) bob.fd(100) -bob.lt(90) +bob.lt(90) bob.fd(100) -bob.lt(90) +bob.lt(90) bob.fd(100) ``` 上面这个太麻烦了,咱们可以用一个for语句来让这个过程更简洁。把下面的代码添加到mypolygon.py中然后运行一下: ```Python -for i in range(4): +for i in range(4): print('Hello!') ``` @@ -104,8 +104,8 @@ Hello! 这就是一个用for语句来画正方形的语句: ```Python -for i in range(4): - bob.fd(100) +for i in range(4): + bob.fd(100) bob.lt(90) ``` @@ -115,7 +115,7 @@ for语句也被叫做循环,因为运行流程会重复执行循环体。在 这次的正方形绘制代码实际上和之前的少有不同了,因为在画完了最后一个边之后,多了一次转向。多出来的这部分需要消耗额外的时间,但简化了下次我们来循环进行绘制的过程。这个版本的代码也有一个额外的效果:让小乌龟回到起点,朝着初始方向。 -##4.3 练习 +## 4.3 练习 下面是一系列使用TurtleWorld的练习。主要就是比较有意思,不过也有一些训练的作用。你做这些练习的时候,一定要注意考虑这些训练的作用。 @@ -133,15 +133,15 @@ for语句也被叫做循环,因为运行流程会重复执行循环体。在 5.在circle基础上做一个叫做arc的函数,在circle的基础上添加一个angle(译者注:角度)变量,用这个角度值来确定画多大的一个圆弧。用度做单位,当angle等于360度的时候,arc函数就应当画出一个整团了。 -##4.4 封装 +## 4.4 封装 第一个练习让你把正方形绘制的代码定义到一个函数里面,然后调用这个函数,传入一个turtle对象作为参数。下面就是个例子了: ```Python -def square(t): - for i in range(4): - t.fd(100) - t.lt(90) +def square(t): + for i in range(4): + t.fd(100) + t.lt(90) square(bob) ``` @@ -156,15 +156,15 @@ square(alice) 用函数的形式把一段代码包装起来,叫做封装。这样有一个好处,就是给代码起了个名字,有类似文档说明的功能,更好理解了。另外一个好处是下次重复使用这段代码的时候,再次调用函数就可以了,这比复制粘贴函数体可方便多了。 -##4.5 泛化 +## 4.5 泛化 下一步就是给square函数添加一个长度参数了。下面是样例: ```Python -def square(t, length): - for i in range(4): - t.fd(length) - t.lt(90) +def square(t, length): + for i in range(4): + t.fd(length) + t.lt(90) square(bob, 100) ``` @@ -173,11 +173,11 @@ square(bob, 100) 下一步也还是泛化。这次就是不光要画正方形了,要画一个多边形,可以指定边数的。下面是样例: ```Python -def polygon(t, n, length): - angle = 360 / n - for i in range(n): - t.fd(length) - t.lt(angle) +def polygon(t, n, length): + angle = 360 / n + for i in range(n): + t.fd(length) + t.lt(angle) polygon(bob, 7, 70) ``` @@ -196,16 +196,16 @@ polygon(bob, n=7, length=70) 这种语法结构让程序更容易被人读懂。也能提醒实际参数和形式参数的使用过程:调用一个函数的时候,把实际参数的值赋给了形式参数。 -##4.6 接口设计 +## 4.6 接口设计 下一步就是写circle这个函数了,需要半径r作为一个参数。下面是一个简单的样例,使用polygon函数来画一个50边形,来接近一个圆: ```Python -import math -def circle(t, r): - circumference = 2 * math.pi * r - n = 50 - length = circumference / n +import math +def circle(t, r): + circumference = 2 * math.pi * r + n = 50 + length = circumference / n polygon(t, n, length) ``` @@ -222,16 +222,16 @@ n是我们用来逼近一个圆所用的线段数量,所以length就是每一 与其让接口复杂冗余,更好的思路是让n根据周长来自适应一个合适的值: ```Python -def circle(t, r): - circumference = 2 * math.pi * r - n = int(circumference / 3) + 1 - length = circumference / n +def circle(t, r): + circumference = 2 * math.pi * r + n = int(circumference / 3) + 1 + length = circumference / n polygon(t, n, length) ``` 现在线段个数就是周长的三分之一了,因此每段线段的长度近似为3,这个大小可以让圆看着不错,也对任意大小的圆都适用了。 -##4.7 重构 +## 4.7 重构 当我写circle这个函数的时候,我能利用多边形函数polygon是因为一个足够多边的多边形和圆很接近。但圆弧就不太适合这个思路了;我们不能用多边形或者圆来画一个圆弧。 @@ -239,43 +239,43 @@ def circle(t, r): 一个替代的方法就是把polygon修改一下,转换成圆弧。结果大概如下所示: ```Python -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) +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) ``` 这个函数的后半段看着和多边形那个还挺像的,但必须修改一下接口才能重利用多边形的代码。我们在多边形函数上增加angle(角度)作为第三个参数,但继续叫多边形就不太合适了,因为不闭合啊!所以就改名叫它多段线polyline: ```Python -def polyline(t, n, length, angle): - for i in range(n): - t.fd(length) +def polyline(t, n, length, angle): + for i in range(n): + t.fd(length) t.lt(angle) ``` 现在就可以用多段线polyline来重写多边形polygon和圆弧arc: ```Python -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 +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) ``` 最终,咱们就可以用圆弧arc来重写circle的实现了: ```Python -def circle(t, r): +def circle(t, r): arc(t, r, 360) ``` @@ -283,7 +283,7 @@ def circle(t, r): 如果我们事先进行了计划,估计就会先写出多段线函数polyline,然后就不用重构了,但大家在开始一个项目之前往往不一定了解的那么清楚。一旦开始编码了,你就逐渐更理解其中的问题了。有时候重构就意味着你已经学到了新的内容了。 -##4.8 开发计划 +## 4.8 开发计划 开发计划是写程序的一系列过程。我们本章所用的就是『封装-泛化』的模式。这一过程的步骤如下: @@ -299,17 +299,17 @@ def circle(t, r): 这个模式有一些缺点,我们后续会看到一些替代的方式,但这个模式是很有用的,尤其对耐饿实现不值得怎么去把程序分成多个函数的情况。 -##4.9 文档字符串 +## 4.9 文档字符串 文档字符串是指:在函数开头部位,解释函数的交互接口的字符串,doc是文档documentation的缩写。下面是一个例子: ```Python -def polyline(t, n, length, angle): +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) +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) ``` @@ -319,7 +319,7 @@ t is a turtle. """ 写这种文档,对交互接口的设计来说,是至关重要的。设计良好的交互接口应该很容易解释明白;如果你的函数有一个特别不好解释了,估计这个函数的交互设计还存在需要改进的地方。 -##4.10 调试 +## 4.10 调试 一个交互接口,就像是函数和调用者的一个中间人。调用者提供特定的参数,函数完成特定的任务。 @@ -331,7 +331,7 @@ t is a turtle. """ 如果前置条件得到了满足,而后置条件未能满足,这个bug就是函数的了。所以如果你的前后置条件都弄清晰,对调试很有帮助。 -##4.11 Glossary 术语列表 +## 4.11 Glossary 术语列表 method: A function that is associated with an object and called using dot notation. @@ -387,8 +387,8 @@ A requirement that should be satisfied by the function before it ends. >后置条件:函数结束之前应该满足的一些要求。 -##4.12 练习 -###练习1 +## 4.12 练习 +## # 练习1 点击下面这个链接[下载代码](http://thinkpython2.com/code/polygon.py)。 1. 画一个栈图,表明运行函数circle(bob,radius)时候程序的状态。你可以手算一下,或者把输出语句加到代码上。 @@ -396,26 +396,25 @@ A requirement that should be satisfied by the function before it ends. 2. 4.7小节中的那个版本的arc函数并不太精确,因为对圆进行线性逼近总会超过真实情况。结果就是小乌龟总会距离正确位置偏离一些像素。我的样例给出了一种降低这种误差程度的方法。阅读一下代码,看你能不能理解。如果你画一个图标,也许就能明白代码是怎么工作的了。 ________________________________________ - ![Turtle flowers](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPythonExercise4.2.png) + ![Turtle flowers and Turtle pies](./images/figure4.1-4.2.jpg) Figure 4.1: Turtle flowers. +Figure 4.2: Turtle pies. ________________________________________ -###练习2 +## # 练习2 写一系列的合适的函数组合,画出图4.1所示的花图案。 [样例]( http://thinkpython2.com/code/flower.py) [以及此链接文件](http://thinkpython2.com/code/polygon.py) ________________________________________ -![Turtle pies](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPythonExercise4.3.png) -Figure 4.2: Turtle pies. ________________________________________ -###练习3 +## # 练习3 写一系列的合适的函数组合,画出图4.2所示的形状。 [样例](http://thinkpython2.com/code/pie.py) -###练习4 +## # 练习4 字母表当中的字母都可以用一定数量的基本元素来构建,比如竖直或者水平的线条,以及一些曲线。设计一个能用最小数量的基本元素画出来的字母表,然后写个函数来画字母出来。 @@ -423,6 +422,6 @@ ________________________________________ 你可以参考这里的[样例](http://thinkpython2.com/code/letters.py);同时还需要[这些](http://thinkpython2.com/code/polygon.py)。 -##练习5 +## 练习5 去[Wiki百科](http://en.wikipedia.org/wiki/Spiral)看一下螺旋线的相关内容;然后写个程序来画阿基米德曲线(曲线中的一种)。[样例](http://thinkpython2.com/code/spiral.py) diff --git a/chapter5.md b/chapter5.md index 1ed3942..670d48e 100644 --- a/chapter5.md +++ b/chapter5.md @@ -1,8 +1,8 @@ -#第五章 条件循环 +# 第五章 条件循环 本章的主题是if语句,就是条件判断,会对应程序的不同状态来执行不同的代码。但首先我要介绍两种新的运算符:floor(地板除法,舍弃小数位)和modulus(求模,取余数) -##5.1 地板除和求模 +## 5.1 地板除和求模 floor除法,中文有一种翻译是地板除法,挺难听,不过凑活了,运算符是两个右斜杠://,与传统除法不同,地板除法会把运算结果的小数位舍弃,返回整值。例如,加入一部电影的时间长度是105分钟。你可能想要知道这部电影用小时来计算是多长。传统的除法运算如下,会返回一个浮点小数: @@ -41,7 +41,7 @@ floor除法,中文有一种翻译是地板除法,挺难听,不过凑活了 如果你用Python2的话,除法是不一样的。在两边都是整形的时候,常规除法运算符/就会进行地板除法,而两边只要有一侧是浮点数就会进行浮点除法。 -##5.2 布尔表达式 +## 5.2 布尔表达式 布尔表达式是一种非对即错的表达式,只有这么两个值,true(真)或者false(假)。下面的例子都用了双等号运算符,这个运算符会判断两边的值是否相等,相等就是True,不相等就是False: @@ -55,25 +55,25 @@ True和False都是特殊的值,属于bool布尔类型;它们俩不是字符 ```Python >>> type(True) - + >>> type(False) ``` 双等号运算符是关系运算符的一种,其他关系运算符如下: ```Python -x != y # x is not equal to y 二者相等 -x> y # x is greater than y 前者更大 -x> y # x is greater than y 前者更大 -x < y # x is less than y 前者更小 -x>= y # x is greater than or equal to y 大于等于 -x>= y # x is greater than or equal to y 大于等于 +x != y # x is not equal to y 二者相等 +x> y # x is greater than y 前者更大 +x> y # x is greater than y 前者更大 +x < y # x is less than y 前者更小 +x>= y # x is greater than or equal to y 大于等于 +x>= y # x is greater than or equal to y 大于等于 x <= y # x is less than or equal to y 小于等于 ``` 虽然这些运算符你可能很熟悉了,但一定要注意Python里面的符号和数学上的符号有一定区别。常见的错误就是混淆了等号=和双等号==。一定要记住单等号=是一个赋值运算符,而双等号==是关系运算符。另外要注意就是大于等于或者小于等于都是等号放到大于号或者小于号的后面,顺序别弄反。 -##5.3 逻辑运算符 +## 5.3 逻辑运算符 逻辑运算符有三种:且,或以及非。这三种运算符的意思和字面意思差不多。比如x>0且x<10,仅当x在0到10之间的时候才为真。 @@ -91,7 +91,7 @@ True ``` 这种灵活性特别有用,不过有的情况下也容易引起混淆。建议你尽量不要这样用,除非你很熟练了。 -##5.4 条件执行 +## 5.4 条件执行 有用的程序必然要有条件检查判断的功能,根据不同条件要让程序有相应的行为。条件语句就让咱们能够实现这种判断。最简单的就是if语句了: @@ -110,9 +110,9 @@ if语句与函数定义的结构基本一样:一个头部,后面跟着缩进 ```Python if x < 0: - pass # TODO: need to handle negative values! + pass # TODO: need to handle negative values! ``` -##5.5 选择执行 +## 5.5 选择执行 if语句的第二种形式就是『选择执行』,这种情况下会存在两种备选的语句,根据条件来判断执行哪一个。语法如下所示: @@ -125,16 +125,16 @@ else: I 如果x除以2的余数为0,x就是一个偶数了,程序就会显示对应的信息。如果条件不成立,那就运行第二条语句。这里条件非真即假,只有两个选择。这些选择也叫『分支』,因为在运行流程上产生了不同的分支。 -##5.6 链式条件 +## 5.6 链式条件 有时我们要面对的可能性不只有两种,需要更多的分支。这时候可以使用连锁条件来实现: ```Python -if x < y: +if x < y: print('x is less than y') -elif x> y: +elif x> y: print('x is greater than y') -else: +else: print('x and y are equal') ``` @@ -151,7 +151,7 @@ elif choice == 'c': 每一个条件都会依次被检查。如果第一个是假,下一个就会被检查,依此类推。如果有一个为真了,相应的分支语句就运行了,这些条件判断的语句就都结束了。如果有一个以上的条件为真,只有先出现的为真的条件所对应的分支语句会运行。 -##5.7 嵌套条件 +## 5.7 嵌套条件 一个条件判断也可以嵌套在另一个条件判断内。上一节的例子可以改写成如下: @@ -193,7 +193,7 @@ if 0 < x < 10: print('x is a positive single-digit number.') ``` (译者注:Python的这种友善度远远超过了C和C++,这也是为何我一直建议国内高校用Python取代C++来给本科生和研究生做编程入门课程。) -##5.8 递归运算 +## 5.8 递归运算 一个函数可以去调用另一个函数;函数来调用自己也是允许的。这就是递归,是程序最神奇的功能之一,现在可能还不好理解为什么,那么来看看下面这个函数为例: @@ -243,10 +243,10 @@ Blastoff! ```Python def print_n(s, n): - if n <= 0: - return - print(s) - print_n(s, n-1) + if n <= 0: + return + print(s) + print_n(s, n-1) s="Python is good" n=4 print_n(s, n) @@ -259,7 +259,7 @@ print_n(s, n) 上面这种简单的例子,实际上用for循环更简单。不过后面我们就会遇到一些用for循环不太好写的例子了,这些情况往往用递归更简单,所以早点学习下递归是有好处的。 -##5.9 递归函数的栈图 +## 5.9 递归函数的栈图 在本书的第三章第九节,我们用栈图来表征函数调用过程中程序的状态。同样是这种栈图,将有助于给大家展示递归函数的运行过程。 @@ -270,7 +270,7 @@ print_n(s, n) 图5.1展示了前面样例中coundown函数在n=3的时候的栈图。 ________________________________________ -![Figure 5.1: Stack diagram.](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPythonFigure5.1.png) +![Figure 5.1: Stack diagram.](./images/figure5.1.jpg) Figure 5.1: Stack diagram. ________________________________________ @@ -282,7 +282,7 @@ ________________________________________ 下面练习一下,画一个print_n函数的栈图,让s为字符串『Hello』,n为2。然后写一个函数,名字为do_n,使用一个操作对象和一个数字n作为实际参数,给出一个n作为次数来调用这个函数。 -##5.10 无穷递归 +## 5.10 无穷递归 如果一个递归一直都不能到达基准条件,那就会持续不断地进行自我调用,程序也就永远不会终止了。这就叫无穷递归,一般这都不是个好事情哈。下面就是一个无穷递归的最简单的例子: @@ -303,7 +303,7 @@ RuntimeError: Maximum recursion depth exceeded 如果你意外写出来一个无穷递归的代码,好好检查一下你的函数,一定要确保有一个基准条件来停止递归调用。如果存在了基准条件,检查一下一定要确保能使之成立。 -##5.11 键盘输入 +## 5.11 键盘输入 目前为止咱们写过的程序还都没有接收过用户的输入。这写程序每次都是做一些同样的事情。 @@ -350,7 +350,7 @@ What do you mean, an African or a European swallow? 稍后我们再来看看如何应对这种错误。 -##5.12 调试 +## 5.12 调试 当语法错误或者运行错误出现的时候,错误信息会包含很多有用的信息,不过信息量太大,太繁杂。最有用的也就下面这两类: @@ -363,10 +363,10 @@ What do you mean, an African or a European swallow? ```Bash>>> x = 5 ->>> y = 6 +>>> y = 6 File "", line 1 - y = 6 - ^ + y = 6 + ^ IndentationError: unexpected indent ``` @@ -401,7 +401,7 @@ ValueError: math domain error 所以你得花点时间仔细阅读错误信息,但不要轻易就认为出错信息说的内容都是完全正确可靠的。 -##5.13 Glossary 术语列表 +## 5.13 Glossary 术语列表 floor division: An operator, denoted //, that divides two numbers and rounds down (toward zero) to an integer. @@ -477,8 +477,8 @@ A recursion that doesn’t have a base case, or never reaches it. Eventually, an >无穷递归:一个没有基准条件的递归,或者永远无法达到基准条件的递归。一般无穷递归总会引起运行错误。 -##5.14 练习 -### 练习1 +## 5.14 练习 +## # 练习1 time模块提供了一个名字同样叫做time的函数,会返回当前格林威治时间的时间戳,就是以某一个时间点作为初始参考值。在Unix系统中,时间戳的参考值是1970年1月1号。 @@ -492,7 +492,7 @@ time模块提供了一个名字同样叫做time的函数,会返回当前格林 写一个脚本,读取当前的时间,把这个时间转换以天为单位,剩余部分转换成小时-分钟-秒的形式,加上参考时间以来的天数。 -### 练习2 +## # 练习2 费马大定理内容为,a、b、c、n均为正整数,在n大于2的情况,下面的等式关系不成立: @@ -507,7 +507,7 @@ time模块提供了一个名字同样叫做time的函数,会返回当前格林 3. 写一个函数来提醒用户要输入a、b、c和n的值,然后把输入值转换为整形变量,接着用check_fermat这个函数来检查他们是否违背了费马大定理。 -### 练习3 +## # 练习3 给你三根木棍,你能不能把它们拼成三角形呢?比如一个木棍是12英寸长,另外两个是1英寸长,这两根短的就不够长,无法拼成三角形了。 @@ -520,7 +520,7 @@ time模块提供了一个名字同样叫做time的函数,会返回当前格林 2. 写一个函数来提示下用户,要输入三遍长度,把它们转换成整形,用is_triangle函数来检测这些给定长度的边能否组成三角形。 -###4 练习4 +## # 4 练习4 下面的代码输出会是什么?画一个栈图来表示一下如下例子中程序输出结果时候的状态。 @@ -538,7 +538,7 @@ recurse(3, 0) 接下来的练习用到了第四章我们提到过的turtle小乌龟模块。 -### 练习5 +## # 练习5 阅读下面的函数,看看你能否弄清楚函数的作用。运行一下试试(参考第四章里面的例子来酌情修改代码)。 @@ -556,11 +556,11 @@ def draw(t, length, n): t.bk(length*n) ``` ________________________________________ -![Figure 5.2: A Koch curve.](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPythonFigure5.2Koch%20curve.png) +![Figure 5.2: A Koch curve.](./images/figure5.2.jpg) Figure 5.2: A Koch curve. ________________________________________ -###6 练习6 +## # 6 练习6 Koch科赫曲线是一种分形曲线,外观如图5.2所示。要画长度为x的这种曲线,你要做的步骤如下: diff --git a/chapter6.md b/chapter6.md index 7e0d7e9..b2447c0 100644 --- a/chapter6.md +++ b/chapter6.md @@ -1,8 +1,8 @@ -#第六章 有返回值的函数 +# 第六章 有返回值的函数 我们已经用过的很多Python的函数,比如数学函数,都会有返回值。但我们写过的函数都是无返回值的:他们实现一些效果,比如输出一些值,或者移动小乌龟,但他们就是不返回值。 -##6.1 返回值 +## 6.1 返回值 对函数进行调用,就会产生一个返回的值,我们一般把这个值赋给某个变量,或者放进表达式中来用。 @@ -64,7 +64,7 @@ None 然后练习一下把,写一个比较大小的函数,用两个之x和y作为参数,如果x大于y返回1,相等返回0,x小于y返回-1. -##6.2 增量式开发 +## 6.2 增量式开发 写一些复杂函数的时候,你会发现要花很多时间调试。 @@ -147,7 +147,7 @@ def distance(x1, y1, x2, y2): 做个练习吧,用这种增量式开发的思路来写一个叫做hypotenuse(斜边)的函数,接收两个数值作为给定两边长,求以两边长为直角边的直角三角形斜边的长度。做练习的时候记得要记录好开发的各个阶段。 -##6.3 组合 +## 6.3 组合 你现在应该已经能够在一个函数里面调用另外一个函数了。下面我们写一个函数作为例子,这个函数需要两个点,一个是圆心,一个是圆周上面的点,函数要用来计算这个圆的面积。 假设圆心的坐标存成一对变量:xc和yc,圆周上一点存成一对变量:xp和yp。第一步就是算出来这个圆的半径,也就是这两个点之间的距离。我们就用之前写过的那个distance的函数来完成这件事: @@ -176,7 +176,7 @@ def circle_area(xc, yc, xp, yp): def circle_area(xc, yc, xp, yp): return area(distance(xc, yc, xp, yp)) ``` -##6.4 布尔函数 +## 6.4 布尔函数 函数也可以返回布尔值,这种情况便于隐藏函数内部的复杂测试。例如: @@ -221,7 +221,7 @@ if is_divisible(x, y) == True: 做一个练习,写一个函数is_between(x, y, z),如果x ≤ y ≤z则返回真,其他情况返回假。 -##6.5 更多递归 +## 6.5 更多递归 我们目前学过的知识Python的一小部分子集,不过这部分子集本身已经是一套完整的编程语言了,这就意味着所有能计算的东西都可以用这部分子集来表达。实际上任何程序都可以改写成只用你所学到的这部分Python特性的代码。(当然你还需要一些额外的代码来控制设备,比如鼠标、磁盘等等,但也就这么多额外需要而已。) @@ -298,13 +298,13 @@ def factorial(n): 图6.1表明了这一系列函数调用过程中的栈图。 ________________________________________ -![Figure 6.1: Stack diagram](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPython6.1.png) +![Figure 6.1: Stack diagram](./images/figure6.1.jpg) Figure 6.1: Stack diagram. ________________________________________ -##6.6 信仰之跃 +## 6.6 信仰之跃 跟随着运行流程是阅读程序的一种方法,但很快就容易弄糊涂。另外一个方法,我称之为『思维跳跃』。当你遇到一个函数调用的时候,你不用去追踪具体的执行流程,而是假设这个函数工作正常并且返回正确的结果。 @@ -320,13 +320,15 @@ ________________________________________ 当然了,当你还没写完一个函数的时候就假设它正常工作确实有点奇怪,不过这也是我们称之为『思维飞跃』的原因了,你总得飞跃一下。 -##6.7 斐波拉契数列 +## 6.7 斐波拉契数列 计算阶乘之后,我们来看看斐波拉契数列,这是一个广泛应用于展示递归定义的数学函数,[定义](http://en.wikipedia.org/wiki/Fibonacci_number如下: - fibonacci(0) = 0 - fibonacci(1) = 1 - fibonacci(n) = fibonacci(n−1) + fibonacci(n−2) +```Tex +fibonacci(0) = 0 +fibonacci(1) = 1 +fibonacci(n) = fibonacci(n−1) + fibonacci(n−2) +``` 翻译成Python的语言大概如下这样: @@ -334,7 +336,7 @@ ________________________________________ def fibonacci (n): if n == 0: return 0 - elif n == 1: + elif n == 1: return 1 else: return fibonacci(n-1) + fibonacci(n-2) @@ -342,7 +344,7 @@ def fibonacci (n): 跃』的方法,如果你假设两个递归调用都正常工作,整个过程就很明确了,你就得到正确答案加到一起即可。 -##6.8 检查类型 +## 6.8 检查类型 如果我们让阶乘使用1.5做参数会咋样? @@ -394,7 +396,7 @@ Factorial is not defined for negative integers. None 在11.4我们还会看到更多的灵活的处理方式,会输出错误信息,并上报异常。 -##6.9 调试 +## 6.9 调试 把大的程序切分成小块的函数,就自然为我们调试建立了一个个的检查点。在不工作的函数里面,有几种导致错误的可能: @@ -431,20 +433,20 @@ def factorial(n): space在这里是一串空格的字符串,是用来缩进输出的。下面就是4的阶乘得到的结果: ```Python - factorial 4 - factorial 3 - factorial 2 - factorial 1 + factorial 4 + factorial 3 + factorial 2 + factorial 1 factorial 0 returning 1 - returning 1 - returning 2 - returning 6 - returning 24 + returning 1 + returning 2 + returning 6 + returning 24 ``` 如果你对执行流程比较困惑,这种输出会有一定帮助。有效率地进行脚手架开发是需要时间的,但稍微利用一下这种思路,反而能够节省调试用的时间。 -##6.10 Glossary 术语列表 +## 6.10 Glossary 术语列表 temporary variable: A variable used to store an intermediate value in a complex calculation. @@ -470,8 +472,8 @@ A programming pattern that uses a conditional statement to check for and handle >守卫:一种编程模式。使用一些条件语句来检验和处理一些有可能导致错误的情况。 -##6.11 练习 -###练习1 +## 6.11 练习 +## # 练习1 为下面的程序画栈图。程序输出会是什么样的? @@ -491,19 +493,19 @@ x = 1 y = x + 1 print(c(x, y+3, x+y)) ``` -###练习2 +## # 练习2 Ackermann阿克曼函数的定义如下: ```Python -A(m, n) = n+1 if m = 0 - A(m−1, 1) if m> 0 and n = 0 - A(m−1, A(m, n−1)) if m> 0 and n> 0. +A(m, n) = n+1 if m = 0 + A(m−1, 1) if m> 0 and n = 0 + A(m−1, A(m, n−1)) if m> 0 and n> 0. ``` 看一下[这个连接](http://en.wikipedia.org/wiki/Ackermann_function)。写一个叫做ack的函数,实现上面这个阿克曼函数。用你写出的函数来计算ack(3, 4),结果应该是125.看看m和n更大一些会怎么样。[样例代码](http://thinkpython2.com/code/ackermann.py). -###练习3 +## # 练习3 回文的词特点是正序和倒序拼写相同给,比如noon以及redivider。用递归的思路来看,回文词的收尾相同,中间部分是回文词。 @@ -530,10 +532,10 @@ def middle(word): 2. 写一个名叫is_palindrome的函数,使用字符串作为实际参数,根据字符串是否为回文词来返回真假。机主,你可以用内置的len函数来检查字符串的长度。 -###练习4 +## # 练习4 一个数字a为b的权(power),如果a能够被b整除,并且a/b是b的权。写一个叫做is_power 的函数接收a和b作为形式参数,如果a是b的权就返回真。注意:要考虑好基准条件。 -###练习5 +## # 练习5 a和b的最大公约数是指能同时将这两个数整除而没有余数的数当中的最大值。 找最大公约数的一种方法是观察,如果当r是a除以b的余数,那么a和b的最大公约数与b和r的最大公约数相等。基准条件是a和0的最大公约数为a。 diff --git a/chapter7.md b/chapter7.md index 258eaf0..d04bae6 100644 --- a/chapter7.md +++ b/chapter7.md @@ -1,8 +1,8 @@ -#第七章 迭代 +# 第七章 迭代 这一章我们讲迭代,简单说就是指重复去运行一部分代码。在5.8的时候我们接触了一种迭代——递归。在4.2我们还学了另外一种迭代——for循环。在本章,我们会见到新的迭代方式:whie语句。但我要先再稍微讲一下变量赋值。 -##7.1 再赋值 +## 7.1 再赋值 你可能已经发现了,对同一个变量可以多次进行赋值。一次新的赋值使得已有的变量获得新的值(也就不再有旧的值了。) @@ -32,8 +32,8 @@ ```Python >>> a = 5 ->>> b = a # a and b are now equal a和b相等了 ->>> a = 3 # a and b are no longer equal 现在a和b就不相等了 +>>> b = a # a and b are now equal a和b相等了 +>>> a = 3 # a and b are no longer equal 现在a和b就不相等了 >>> b 5 ``` @@ -44,10 +44,10 @@ 对变量进行再赋值总是很有用的,但你用的时候要做好备注和提示。如果变量的值频繁变化,就可能让代码难以阅读和调试。 ________________________________________ -![Figure 7.1: State diagram.](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPython7.1jpg.jpg) +![Figure 7.1: State diagram.](./images/figure7.1.jpg) Figure 7.1: State diagram. ________________________________________ -##7.2 更新变量 +## 7.2 更新变量 最常见的一种再赋值就是对变量进行更新,这种情况下新的值是在旧值基础上进行修改得到的。 @@ -70,7 +70,7 @@ NameError: name 'x' is not defined 对一个变量每次加1也可以叫做一种递增,每次减去1就可以叫做递减了。 -##7.3 循环:While语句 +## 7.3 循环:While语句 计算机经常被用来自动执行一些重复的任务。重复同样的或者相似的任务,而不出错,这是计算机特别擅长的事情,咱们人就做不到了。在一个计算机程序里面,重复操作也被叫做迭代。 @@ -124,7 +124,7 @@ def sequence(n): 参考[维基百科](http://en.wikipedia.org/wiki/Collatz_conjecture) 做一个练习,把5.8里面的那个n次打印函数print_n用迭代的方法来实现。 -##7.4 中断 +## 7.4 中断 有时候你不知道啥时候终止循环,可能正好在中间循环体的时候要终止了。这时候你就可以用break语句来跳出循环。 比如,假设你要让用户输入一些内容,当他们输入done的时候结束。你就可以用如下的方法实现: @@ -152,7 +152,7 @@ Done! 这种while循环的写法很常见,因为这样你可以在循环的任何一个部位对条件进行检测,而不仅仅是循环的头部,你可以确定地表达循环停止的条件(在这种情况下就停止了),而不是消极地暗示『程序会一直运行,直到某种情况』。 -##7.5 平方根 +## 7.5 平方根 循环经常被用于进行数值运算的程序中,这种程序往往是有一个近似值作为初始值,然后逐渐迭代去改进以接近真实值。 @@ -222,7 +222,7 @@ if abs(y-x) < epsilon: ``` 这里可以让epsilon的值为like 0.0000001,差值比这个小就说明已经足够接近了。 -##7.6 算法 +## 7.6 算法 牛顿法是算法的一个例子:通过一系列机械的步骤来解决一类问题(在本章中是用来计算平方根)。 @@ -236,7 +236,7 @@ if abs(y-x) < epsilon: 有的事情人们平时做起来很简单,甚至都不用思考,这些事情往往最难用算法来表达。比如理解自然语言就是个例子。我们都能理解自然语言,但目前为止还没有人能解释我们到底是怎么做到的,至少没有人把这个过程归纳出算法的形式。 -##7.7 调试 +## 7.7 调试 现在你已经开始写一些比较大的程序了,你可能发现自己比原来花更多时间来调试了。代码越多,也就意味着出错的可能也越大了,bug也有了更多的藏身之处了。 @@ -248,7 +248,7 @@ if abs(y-x) < epsilon: 在实际操作当中,程序中间位置并不是总那么明确,也未必就很容易去检查。所以不用数行数来找确定的中间点。相反的,只要考虑一下程序中哪些地方容易调试,然后哪些地方进行检验比较容易就行了。然后你就在你考虑好的位置检验一下看看bug是在那个位置之前还是之后。 -##7.8 Glossary 术语列表 +## 7.8 Glossary 术语列表 reassignment: Assigning a new value to a variable that already exists. @@ -289,8 +289,8 @@ A general process for solving a category of problems.>算法:解决某一类问题的一系列通用的步骤。 -##7.9 练习 -###练习1 +## 7.9 练习 +## # 练习1 从7.5复制一个循环,然后改写成名字叫做mysqrt的函数,该函数用一个a作为参数,选择一个适当的起始值x,然后返回a的平方根的近似值。 @@ -300,7 +300,7 @@ A general process for solving a category of problems. 第一列是数a;第二列是咱们自己写的函数mysqrt计算出来的平方根,第三行是用Python内置的math.sqrt函数计算的平方根,最后一行是这两者的差值的绝对值。 -###练习2 +## # 练习2 Python的内置函数eval接收字符串作为参数,然后用Python的解释器来运行。例如: @@ -319,7 +319,7 @@ Python的内置函数eval接收字符串作为参数,然后用Python的解释 这个程序要一直运行,直到用户输入『done』才停止,然后输出最后一次计算的表达式的值。 -##练习3 +## 练习3 传奇的数学家拉马努金发现了一个无穷级数(1914年的论文),能够用来计算圆周率倒数的近似值: diff --git a/chapter8.md b/chapter8.md index c9e5d86..ed37aad 100644 --- a/chapter8.md +++ b/chapter8.md @@ -1,8 +1,8 @@ -#第八章 字符串 +# 第八章 字符串 字符串和整形、浮点数以及布尔值很不一样。一个字符串是一个序列,意味着是对其他值的有序排列。在本章你将学到如何读取字符串中的字符,你还会学到一些字符串相关的方法。 -##8.1 字符串是序列 +## 8.1 字符串是序列 字符串就是一串有序的字符。你可以通过方括号操作符,每次去访问字符串中的一个字符: @@ -50,7 +50,7 @@ >>> letter = fruit[1.5] TypeError: string indices must be integers ``` -##8.2 len 长度 +## 8.2 len 长度 len 是一个内置函数,会返回一个字符串中字符的长度: @@ -75,7 +75,7 @@ IndexError: string index out of range ``` 或者你也可以用负数索引,意思就是从字符串的末尾向前数几位。fruit[-1]这个表达式给你最后一个字符,fruit[-2]给出倒数第二个,依此类推。 -##8.3 用 for 循环遍历字符串 +## 8.3 用 for 循环遍历字符串 很多计算过程都需要每次从一个字符串中拿一个字符。一般都是从头开始,依次得到每个字符,然后做点处理,然后一直到末尾。这种处理模式叫遍历。写一个遍历可以使用 while 循环: @@ -121,7 +121,7 @@ Jack Kack Lack Mack Nack Oack Pack Qack 当然了,有点不准确的地方,因为有"Ouack"和 "Quack"两处拼写错了。做个练习,修改一下程序,改正这个错误。 -##8.4 字符串切片 +## 8.4 字符串切片 字符串的一段叫做切片。从字符串中选择一部分做切片,与选择一个字符有些相似: @@ -136,7 +136,7 @@ Jack Kack Lack Mack Nack Oack Pack Qack [n:m]这种操作符,会返回字符串中从第『n』个到第『m』个的字符,包含开头的第『n』个,但不包含末尾的第『m』个。这个设计可能有点违背直觉,但可能有助于想象这个切片在字符串中的方向,如图8.1。 ________________________________________ -![Figure 8.1](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPythonFigure8.1.png) +![Figure 8.1](./images/figure8.1.jpg) Figure 8.1: Slice indices. ________________________________________ @@ -160,7 +160,7 @@ ________________________________________ 那么来练习一下,你觉得 fruit[:]这个是什么意思?在程序中试试吧。 -##8.5 字符串不可修改 +## 8.5 字符串不可修改 大家总是有可能想试试把方括号在赋值表达式的等号左侧,试图去更改字符串中的某一个字符。比如: @@ -184,7 +184,7 @@ TypeError: 'str' object does not support item assignment 上面的例子中,对 greeting 这个字符串进行了切片,然后添加了一个新的首字母过去。这并不会对原始字符串有任何影响。(译者注:也就是 greeting 这个字符串的值依然是原来的值,是不可改变的。) -##8.6 搜索 +## 8.6 搜索 下面这个函数是干啥的? ```Python @@ -201,18 +201,18 @@ def find(word, letter): ```Python # 改进的find函数,利用列表收集字母letter在单词word中出现的全部位置. def find(word, letter): - index = 0 + index = 0 - result_list=[] + result_list=[] - while index < len(word): - if word[index] == letter: - - result_list.append(index) - - index = index + 1 + while index < len(word): + if word[index] == letter: + + result_list.append(index) + + index = index + 1 - return result_list + return result_list find('banana','a') ``` @@ -226,7 +226,7 @@ find('banana','a') 做个练习,修改一下 find 函数,加入第三个参数,这个参数为查找开始的字符串位置。 -##8.7 循环和计数 +## 8.7 循环和计数 下面这个程序计算了字母 a 在一个字符串中出现的次数: @@ -245,7 +245,7 @@ for letter in word: 然后再重写一下这个函数,这次不再让它遍历整个字符串,而使用上一节中练习的三参数版本的 find 函数。 -##8.8 字符串方法 +## 8.8 字符串方法 字符串提供了一些方法,这些方法能够进行很多有用的操作。方法和函数有些类似,也接收参数然后返回一个值,但语法稍微不同。比如,upper 这个方法就读取一个字符串,返回一个全部为大写字母的新字符串。 @@ -297,7 +297,7 @@ A method call is called an invocation;方法的调用被叫做——调用(译 这个搜索失败了,因为 b 并没有在索引1到2且不包括2的字符中间出现。搜索到指定的第三个变量作为索引的位置,但不包括该位置,这就让 find 方法与切片操作符相一致。 -##8.9 运算符 in +## 8.9 运算符 in in 这个词在字符串操作中是一个布尔操作符,它读取两个字符串,如果前者的字符串为后者所包含,就返回真,否则为假: @@ -321,7 +321,7 @@ def in_both(word1, word2):>>> in_both('apples', 'oranges') a e s ``` -##8.10 字符串比较 +## 8.10 字符串比较 关系运算符对于字符串来说也可用。比如可以看看两个字符串是不是相等: @@ -346,7 +346,7 @@ Python 对大小写字母的处理与人类常规思路不同。所有大写字 一个解决这个问题的普遍方法是把字符串转换为标准格式,比如都转成小写的,然后再进行比较。一定要记得哈,以免你遇到一个用 Pineapple 武装着自己的家伙的时候手足无措。 -##8.11 调试 +## 8.11 调试 使用索引来遍历一个序列中的值的时候,弄清楚遍历的开头和结尾很不容易。下面这个函数用来对比两个单词,如果一个是另一个的倒序就返回真,但这个函数代码中有两处错误: @@ -373,14 +373,14 @@ i 和 j 都是索引:i 从头到尾遍历单词 word1,而 j 逆向遍历单 ```Python >>> is_reverse('pots', 'stop') - ... File "reverse.py", line 15, in is_reverse if word1[i] != word2[j]: IndexError: string index out of range + ... File "reverse.py", line 15, in is_reverse if word1[i] != word2[j]: IndexError: string index out of range ``` 为了改正这个错误,第一步就是在出错的那行之前先输出索引的值。 ```Python while j> 0: - print(i, j) # print here + print(i, j) # print here if word1[i] != word2[j]: return False i = i+1 @@ -406,7 +406,7 @@ True 这次我们得到了正确的结果,但似乎循环只走了三次,这有点奇怪。为了弄明白带到怎么回事,我们可以画一个状态图。在第一次迭代的过程中,is_reverse 的框架如图8.2所示。 ________________________________________ -![Figure 8.2](http://7xnq2o.com1.z0.glb.clouddn.com/ThinkPythonFigure8.2.png) +![Figure 8.2](./images/figure8.2.jpg) Figure 8.2: State diagram. ________________________________________ @@ -414,7 +414,7 @@ ________________________________________ 从这个图上运行的程序,文件,更改这些值I和J在每一次迭代过程。发现并解决此函数中的二次错误。 -##8.12 Glossary 术语列表 +## 8.12 Glossary 术语列表 object: Something a variable can refer to. For now, you can use "object" and "value" interchangeably. @@ -475,17 +475,17 @@ A function or method argument that is not required. >可选参数:一个函数或者方法中有一些参数是可选的,非必需的。 -##8.13 练习 -### 练习1 +## 8.13 练习 +## # 练习1 -阅读 [这里](http://docs.python.org/3/library/stdtypes.html#string-methods)关于字符串的文档。你也许会想要试试其中一些方法,来确保你理解它们的意义。比如 strip 和 replace 都特别有用。 +阅读 [这里](http://docs.python.org/3/library/stdtypes.html# string-methods)关于字符串的文档。你也许会想要试试其中一些方法,来确保你理解它们的意义。比如 strip 和 replace 都特别有用。 文档的语法有可能不太好理解。比如在find 这个方法中,方括号表示了可选参数。所以 sub 是必须的参数,但 start 是可选的,如果你包含了 start,end 就是可选的了。 -### 练习2 +## # 练习2 字符串有个方法叫 count,与咱们在8.7中写的 count 函数很相似。 阅读一下这个方法的文档,然后写一个调用这个方法的代码,统计一下 banana 这个单词中 a 出现的次数 。 -### 练习3 +## # 练习3 字符串切片可以使用第三个索引,作为步长来使用;步长的意思就是取字符的间距。一个步长为2的意思就是每隔一个取一个字符;3的意思就是每次取第三个,以此类推。 ```Python @@ -497,7 +497,7 @@ A function or method argument that is not required. 使用这个方法把练习三当中的is_palindrome写成一个一行代码的版本。 -### 练习4 +## # 练习4 下面这些函数都试图检查一个字符串是不是包含小写字母,但他们当中肯定有些是错的。描述一下每个函数真正的行为(假设参数是一个字符串)。 ```Python @@ -528,7 +528,7 @@ def any_lowercase5(s): return False return True ``` -### 练习5 +## # 练习5 凯撒密码是一种简单的加密方法,用的方法是把每个字母进行特定数量的移位。对一个字母移位就是把它根据字母表的顺序来增减对应,如果到末尾位数不够就从开头算剩余的位数,『A』移位3就是『D』,而『Z』移位1就是『A』了。 diff --git a/chapter9.md b/chapter9.md index 55d26d9..be113fd 100644 --- a/chapter9.md +++ b/chapter9.md @@ -1,8 +1,8 @@ -#第九章 案例学习:单词游戏 +# 第九章 案例学习:单词游戏 本章我们进行第二个案例学习,这一案例中涉及到了用搜索具有某些特征的单词来猜谜。比如,我们会发现英语中最长的回文词,然后搜索那些按照单词表顺序排列字母的单词。我还会给出一种新的程序开发计划:降低问题的复杂性和难度,还原到以前解决的问题。 -##9.1 读取字符列表 +## 9.1 读取字符列表 本章练习中,咱们需要用一个英语词汇列表。网上有很多,不过最适合我们的列表并且是共有领域的,莫过于 Grady Ward这份词汇表,这是Moby词典计划的一部分(点击[此链接访问详情](http://wikipedia.org/wiki/Moby_Project))。这是一份113,809个公认的字谜表;也就是公认可以用于字谜游戏以及其他文字游戏的单词。在 Moby 的词汇项目中,该词表的文件名为113809of.fic;你可以下载一份副本,这里名字简化成 words.txt 了,下载地址[在这里](http://thinkpython2.com/code/words.txt)。 @@ -46,15 +46,15 @@ fin = open('words.txt') word = line.strip() print(word) ``` -##9.2 练习 +## 9.2 练习 下面这些练习都有样例代码。不过你最好在看答案之前先自己对每个练习都尝试一下。 -###练习1 +## # 练习1 写一个程序读取 words.txt,然后只输出超过20个字母长度的词(这个长度不包括转义字符)。 -###练习2 +## # 练习2 在1939年,作家厄尔尼斯特·文森特·莱特曾经写过一篇5万字的小说《葛士比》,里面没有一个字母e。因为在英语中 e 是用的次数最多的字母,所以这很不容易的。事实上,不使用最常见的字符,都很难想出一个简单的想法。一开始很慢,不过仔细一些,经过几个小时的训练之后,你就逐渐能做到了。 @@ -62,22 +62,22 @@ fin = open('words.txt') 修改一下上一节的程序代码,让它只打印单词表中没有 e 的词汇,并且统计一下这些词汇在总数中的百分比例。 -###练习3 +## # 练习3 写一个名叫 avoids 的函数,接收一个单词和一个禁用字母组合的字符串,如果单词不含有该字符串中的任何字母,就返回真。 修改一下程序代码,提示用户输入一个禁用字母组合的字符串,然后输入不含有这些字母的单词数目。你能找到5个被禁用字母组合,排除单词数最少吗? -###练习4 +## # 练习4 写一个名叫uses_only的函数,接收一个单词和一个字母字符串,如果单词仅包含该字符串中的字母,就返回真。你能仅仅用 acefhlo 这几个字母造句子么?或者试试『Hoe alfalfa』? -###练习5 +## # 练习5 写一个名字叫uses_all的函数,接收一个单词和一个必需字母组合的字符串,如果单词对必需字母组合中的字母至少都用了一次就返回真。有多少单词都用到了所有的元音字母 aeiou?aeiouy的呢? -###练习6 +## # 练习6 写一个名字叫is_abecedarian的函数,如果单词中所有字母都是按照字母表顺序出现就返回真(重叠字母也是允许的)。有多少这样的单词? -##9.3 搜索 +## 9.3 搜索 刚刚的那些练习都有一些相似之处:都可以用我们在8.6学过的搜索来解决。下面是一个最简化的例子: @@ -134,7 +134,7 @@ def uses_all(word, required): ``` 、这就是一种新的程序开发规划模式,就是降低问题的复杂性和难度,还原到以前解决的问题,意思是你发现正在面对的问题是之前解决过的问题的一个实例,就可以用已经存在的方案来解决。 -##9.4 用索引循环 +## 9.4 用索引循环 上面的章节中我写了各种用 for 循环的函数,因为当时只需要字符串中的字符;这就不需要理会索引。 @@ -205,7 +205,7 @@ def is_palindrome(word): 这里的is_reverse这个函数在第8章第11节讲过哈。 -##9.5 调试 +## 9.5 调试 测试程序很难的。本章的函数相对来说还算容易测试,因为你可以手动计算来检验结果。即便如此,选择一系列单词然后检测所有可能的错误,可能不仅是做起来困难,甚至都是不可能完成的任务。 @@ -228,7 +228,7 @@ def is_palindrome(word): >— Edsger W. Dijkstra -##9.6 Glossary 术语列表 +## 9.6 Glossary 术语列表 file object: A value that represents an open file. @@ -244,15 +244,15 @@ A test case that is a typical or non-obvious (and less likely to be handled corr >特殊案例:很典型或者不明显的测试用的案例,往往都很不容易正确处理。 -##9.7 练习 -###练习7 +## 9.7 练习 +## # 练习7 [这个问题](http://www.cartalk.com/content/puzzlers)基于一个谜语,这个谜语在广播节目 Car Talk 上面播放过: 给我一个有三个连续双字母的单词。我会给你一对基本符合的单词,但并不符合。例如, committee 这个单词,C O M M I T E。如果不是有单独的一个 i 在里面,就基本完美了,或者Mississippi 这个词:M I S I S I P I。如果把这些个 i 都去掉就好了。但有一个词正好是三个重叠字母,而且据我所知这个词可能是唯一一个这样的词。当然有有可能这种单词有五百多个呢,但我只能想到一个。是哪个词呢?写个程序来找一下这个词吧。 [样例代码](http://thinkpython2.com/code/cartalk1.py) -###练习8 +## # 练习8 [这个](http://www.cartalk.com/content/puzzlers)又是一个 Car Talk 谜语: @@ -266,7 +266,7 @@ A test case that is a typical or non-obvious (and less likely to be handled corr 写个 Python 程序来检测一下所有的六位数,然后输出一下满足这些要求的数字。 [样例代码](http://thinkpython2.com/code/cartalk2.py) -###练习9 +## # 练习9 [这个](http://www.cartalk.com/content/puzzlers)又是一个 Car Talk 谜语,你可以用搜索来解决: 最近我看望了妈妈,然后我们发现我的年龄反过来正好是她的年龄。例如,假如她是73岁,我就是37岁了。我们好奇这种情况发生多少次,但中间叉开了话题,没有想出来这个问题的答案。 diff --git a/images/figure10.1.jpg b/images/figure10.1.jpg new file mode 100644 index 0000000..b543d05 Binary files /dev/null and b/images/figure10.1.jpg differ diff --git a/images/figure10.2.jpg b/images/figure10.2.jpg new file mode 100644 index 0000000..563dc1b Binary files /dev/null and b/images/figure10.2.jpg differ diff --git a/images/figure10.3.jpg b/images/figure10.3.jpg new file mode 100644 index 0000000..c0b2577 Binary files /dev/null and b/images/figure10.3.jpg differ diff --git a/images/figure10.4.jpg b/images/figure10.4.jpg new file mode 100644 index 0000000..1048038 Binary files /dev/null and b/images/figure10.4.jpg differ diff --git a/images/figure10.5.jpg b/images/figure10.5.jpg new file mode 100644 index 0000000..400b642 Binary files /dev/null and b/images/figure10.5.jpg differ diff --git a/images/figure11.1.jpg b/images/figure11.1.jpg new file mode 100644 index 0000000..7b88f5b Binary files /dev/null and b/images/figure11.1.jpg differ diff --git a/images/figure11.2.jpg b/images/figure11.2.jpg new file mode 100644 index 0000000..612e983 Binary files /dev/null and b/images/figure11.2.jpg differ diff --git a/images/figure12.1.jpg b/images/figure12.1.jpg new file mode 100644 index 0000000..fee7b11 Binary files /dev/null and b/images/figure12.1.jpg differ diff --git a/images/figure12.2.jpg b/images/figure12.2.jpg new file mode 100644 index 0000000..dcb3768 Binary files /dev/null and b/images/figure12.2.jpg differ diff --git a/images/figure15.1.jpg b/images/figure15.1.jpg new file mode 100644 index 0000000..2a1da84 Binary files /dev/null and b/images/figure15.1.jpg differ diff --git a/images/figure15.2.jpg b/images/figure15.2.jpg new file mode 100644 index 0000000..a0be108 Binary files /dev/null and b/images/figure15.2.jpg differ diff --git a/images/figure15.3.jpg b/images/figure15.3.jpg new file mode 100644 index 0000000..51ba494 Binary files /dev/null and b/images/figure15.3.jpg differ diff --git a/images/figure16.1.jpg b/images/figure16.1.jpg new file mode 100644 index 0000000..7103817 Binary files /dev/null and b/images/figure16.1.jpg differ diff --git a/images/figure18.1.jpg b/images/figure18.1.jpg new file mode 100644 index 0000000..5e95cf0 Binary files /dev/null and b/images/figure18.1.jpg differ diff --git a/images/figure18.2.jpg b/images/figure18.2.jpg new file mode 100644 index 0000000..aafd8f4 Binary files /dev/null and b/images/figure18.2.jpg differ diff --git a/images/figure2.1.jpg b/images/figure2.1.jpg new file mode 100644 index 0000000..8999a0b Binary files /dev/null and b/images/figure2.1.jpg differ diff --git a/images/figure3.1.jpg b/images/figure3.1.jpg new file mode 100644 index 0000000..a2ba59a Binary files /dev/null and b/images/figure3.1.jpg differ diff --git a/images/figure4.1-4.2.jpg b/images/figure4.1-4.2.jpg new file mode 100644 index 0000000..5c871f4 Binary files /dev/null and b/images/figure4.1-4.2.jpg differ diff --git a/images/figure5.1.jpg b/images/figure5.1.jpg new file mode 100644 index 0000000..ca96aae Binary files /dev/null and b/images/figure5.1.jpg differ diff --git a/images/figure5.2.jpg b/images/figure5.2.jpg new file mode 100644 index 0000000..f68795e Binary files /dev/null and b/images/figure5.2.jpg differ diff --git a/images/figure6.1.jpg b/images/figure6.1.jpg new file mode 100644 index 0000000..9461959 Binary files /dev/null and b/images/figure6.1.jpg differ diff --git a/images/figure7.1.jpg b/images/figure7.1.jpg new file mode 100644 index 0000000..91bab97 Binary files /dev/null and b/images/figure7.1.jpg differ diff --git a/images/figure8.1.jpg b/images/figure8.1.jpg new file mode 100644 index 0000000..7dbda05 Binary files /dev/null and b/images/figure8.1.jpg differ diff --git a/images/figure8.2.jpg b/images/figure8.2.jpg new file mode 100644 index 0000000..8cb422b Binary files /dev/null and b/images/figure8.2.jpg differ

AltStyle によって変換されたページ (->オリジナル) /