像计算机科学家一样思考Python(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-23 19:55:37

点击下载

作者:[美]Allen B. Downey 著

出版社:人民邮电出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

像计算机科学家一样思考Python

像计算机科学家一样思考Python试读:

前言

本书的奇特历史

1999年,我正在为一门Java的编程入门课程备课。这门课我已经教过3个学期,感到有些灰心。课程的不及格率太高,即使是那些及格的学生,也只获得了很低的成就。

我发现问题之一是教材。它们太厚,有太多冗余的细节,而针对编程技巧的高阶的指导却很不足。并且学生们都受着“陷阱效应”的苦恼:开头时很容易,也能循序渐进,但接着在第5章左右,整个地板就突然陷落了。新资讯来得太多、来得太快,以至于我必须花费一学期剩下的全部时间来帮助他们拾回丢失的片段。

开课前两周,我决定自己来编写教材。我的目标有以下几个。● 尽量简短。学生读10页书,比不读50页书要好。● 注意词汇。我尝试尽量少用术语,并在第一次使用它们时做好定

义。● 循序渐进。为了避免陷阱效应,我抽出了最困难的课题,并把它

们划分成更细的学习步骤。● 专注于编程,而不是编程语言。我只注意包涵了Java的最小的可

用子集,而忽略掉其他。

我需要一个标题,所以心血来潮选择了How to Think Like a Computer Scientist。

第一版教材很粗糙,但确实有效。学生们读完课本,懂得了足够的基础知识,以至我甚至可以利用课堂时间和他们一起讨论更难、更有趣的话题,并且(最重要的是)可以让学生们有足够的时间在课堂上做练习。

我将这本书按照GNU自由文档许可协议(GNU Free Documentation License)发布,让用户可以复制、修改和分发本书。

接下来发生了最酷的事情。Jeff Elkner,弗吉尼亚州的一位高中老师,使用了我的书,并且将其翻译成Python语言的版本。他寄给我他的翻译副本,于是我有了一次很奇特的经历——通过读我自己的书来学习Python。通过绿茶出版社(Green Tea Press),在2001年我出版了第一个Python版本。

2003年,我开始在欧林学院(Olin College)教学,并第一次需要教授Python语言。和Java的对比非常惊人。学生们困扰更少,学会得更多,从事更有意思的项目,总的来说得到了更多的乐趣。

结果就产生了本书,并使用了不那么宏伟堂皇的书名:Think Python。部分改动如下所述。● 我在每章的结尾添加了一节关于调试的说明。这些章节描述寻找

和避免bug的通用技巧,并警示Python中容易出错的误区。● 我增加了更多的练习,小到简短的理解性测试,大到几个实际工

程。并且编写了大部分练习的解答。● 我添加了一系列案例研究——较长的示例,包括练习、解答以

及讨论。其中有些是基于Swampy——我为Python课程所写的一

套代码。Swampy、代码示例以及它们的解答,可以在http://

thinkpython.com上找到。● 我扩展了关于程序开发计划和基础设计模式的讨论。● 我增加了关于调试、算法分析和使用Lumpy画UML图的附录。

我希望你喜欢这本书,并希望它至少能提供一点帮助,助你学会像计算机科学家那样编程和思考。—— Allen B. DowneyNeedham,MA关于作者

Allen Downey是欧林工程学院的计算机科学教授。他曾在韦尔斯利学院、科尔比学院和加州大学伯克利分校教授计算机科学课程。他从加州大学伯克利分校获得计算机科学博士学位,并拥有MIT的硕士和学士学位。关于封面

本书封面的动物是卡罗来纳鹦鹉,也叫卡罗来纳长尾鹦鹉(学名Conuropsis carolinensis)。这种鹦鹉分布于美国东南部,最北到达纽约和大湖区,但主要分布在佛罗里达州到卡罗来纳州一带。

卡罗来纳鹦鹉主色是绿色,头部黄色,成熟时前额和两颊会出现一些橙红色的条纹。 它的平均尺寸是31~33cm。它叫声狂暴而巨大,并且在捕食过程中会喋喋不休。

它居住在沼泽与河畔的树洞中。卡罗来纳鹦鹉是喜欢群居的生物,平时以小群体形式生活,在捕食时可以达到几百只。

不幸的是,这些捕食过程往往在农田的庄稼地里进行,农夫会射击它们,以免破坏庄稼。它们的群体特性让它们会集体救助受伤的鹦鹉,结果让农夫可以一次杀光整群鹦鹉。不但如此,它们的羽毛被用做妇女的帽饰,也有一些鹦鹉被作为宠物。这些因素组合起来,导致在19世纪晚期,卡罗来纳鹦鹉变得非常稀少,并且禽类疾病也加剧了它们的减少。到1920年左右,这个物种灭绝了。

今天,全世界的博物馆中保存了700多只卡罗来纳鹦鹉的标本。

封面图片来自《约翰逊的自然历史》(Johnson’s Natural History)。译后记《像计算机科学家一样思考》这一系列书,早有耳闻,它可谓开创了程序设计入门书的一个新思路。授人以鱼,不若授人以渔;教人编程,不如引导人思考;教人语言细节,不若指明语言精要。而结合Python语言之后,得到的《像计算机科学家一样思考Python》这本书,则是在这个思路上走到了一个极致的佳作。

我是工作之后才开始接触Python的。那时候已经接触过C/C++、Java、C#等传统风格的语言,再看到Python,不免耳目一新。为何以前觉得难以理解的程序设计理念,在Python中的表达却这么简洁而易懂?为何以往需要好多行代码绞尽脑汁才能编写出来的功能,在Python里却只需要几个简单调用即可?为何繁复的集合操作,在Python中却只需要一行for循环语句就完成了?为何Python的文档那么容易找,还可以使用交互模式轻松尝试?每次使用Python编写程序之后,总会感慨,当初初学程序设计语言的时候,如果教的是Python该多好。相信所有学过C/C++之后再接触Python、Ruby、Haskell、Lisp等类似语言的人,都会有相同的感受吧。

那么是什么原因让C/C++几乎垄断了程序设计语言的教材呢?历史惯性。在计算机科学教育开始普及的20世纪70、80年代,C语言正在其鼎盛时期,几乎所有的人都在用C开发程序,操作系统、软件、游戏几乎都是用C甚至汇编开发的。硬件的限制,让那些更抽象、更高阶的语言,无法普及开来。因此教学自然也使用它。久而久之形成了惯性,到了新世纪,程序设计的教学已经赶不上语言发展的潮流了。我们的程序越来越复杂,越来越像人脑,而教学的语言仍然在使用最贴近机器语言的C。而C++、Java、C#不过是在这一惯性上多走了五十步而已。

本书正是扭转这种矛盾局面的一个有益的尝试。《像计算机科学家一样思考》是对程序设计教学模式的真谛的领悟,而使用Python这种简洁强大的高阶语言,也正是这种新思路最贴切的贯彻。授人以渔,自然应当用最好的渔具;引导人思考,当然也应使用更贴近人的思路而不是机器思路的语言。Python在高阶语言中,是一个从理念和实际综合考量后非常合适的候选。

在翻译过程中我发现,本书不但思路很贴切其教学主旨,从行文和用例来看也非常浅显易懂。全书讲了非常多的程序设计理念,在读过之后却会觉得那些理念都很自然,大概也是因为作者苦心安排,前后穿插,让读者能循序渐进地明白每个程序设计理念是因为什么出现的原因吧。这种风格,再配合上精心编辑的示例,用于介绍任何程序设计语言,都是非常合适的。

如果将来我的孩子愿意学习程序设计,我愿意用这本书教他。

尽管我已尽最大努力用心使译文准确、完善,但仍然难免有疏漏之处,如发现问题,欢迎批评指正。电子邮箱 zhaopuming@gmail.com。致谢

非常感谢Jeff Elkner,他将我的Java书翻译成Python,致使我开始这个项目,并向我介绍了Python语言,结果成为我最爱的编程语言。

另外感谢Chris Meyers,他在How to Think Like a Computer Scientist一书中贡献了好几章。

感谢自由软件基金会(Free Software Foundation)开发了GNU自由文档协议,让我和Jeff以及Chris的合作成为可能。感谢创用CC(Creative Commons)开发了我们现在使用的协议。

感谢Lulu,负责How to Think Like a Computer Scientist的编辑。

感谢所有参与了本书早期版本编写的学生,以及所有(下面列出的)贡献者提供的修订和建议。贡献者列表

在最近几年中,超过100名眼光犀利、思维敏捷的读者给我寄来了建议和修订。他们对这个项目的贡献和热情,对我是极大的帮助。如果你有建议或者修订意见,请发邮件到feedback@thinkpython.com。如果我根据你的回馈做出了修改,会将你加入到贡献者列表中(除非你要求被隐藏)。

如果你给出错误出现的位置的部分语句,会让我更容易搜索。页码或者章节号码也可以,但并不那么容易处理。谢谢!● Lloyd Hugh Allen对8.4节提出了修订建议。● Yvon Boulianne对第5章提出了一个语义错误的修订建议。● Fred Bremmer对2.1节提出了一个修订建议。● Jonah Cohen编写了Perl脚本将本书的LaTeX源码转换成美丽的

HTML。● Michael Conlon提出了第2章的一个语法错误,并提出第1章的格

式改进,并且他开启了对解释器的技术讨论。● Benoit Girard寄来一个对5.6节的有趣的修订。● Courtney Gleason 和 Katherine Smith 编写了horsebet.py,在本

书的早期版本中作为一个案例研究。他们的程序现在可以在网站

上找到。● Lee Harr提交了很多修订建议,我们没有空间在这里一一列出,

并且他确实应当被列为本书的一位主要编辑。● James Kaylin是一名使用本书的学生。他提交了许多修订。● David Kershaw 修正了3.10节中错误的catTwice函数。● Eddie Lam提出了第1、2、3章的很多修订建议,他也修正了

Makefie,这样第一次运行时会自动建立索引。他也帮助我们设

置了一个版本管理方案。● Man-Yong Lee 寄来了对2.4节中的示例代码的修订。● David Mayo指出第1章中的单词“unconsciously”需要被修改为“subconsciously”。● Chris McAloon 寄来了对3.9节和3.10节的一些修订。● Matthew J. Moelter是本书的长期贡献者,提出了很多修订建议。● Simon Dicon Montford报告了第3章中缺失的函数定义以及几个错

别字。他也发现了第13章中的increment函数的错误。● John Ouzts 修正了第3章中“返回值”的定义。● Kevin Parks对关于本书如何分布提出了有价值的评论和建议。● David Pool 发来了第1章中术语表中的错别字,以及鼓励的赞美

之言。● Michael Schmitt寄来了关于文件和异常的章节的修订建议。● Robin Shaw指出了13.1节中的一个错误,printTime函数在一个示

例中没有定义就使用了。● Paul Sleigh在第7章中找到一个错误,并发现了Jonah Cohen用于

生成HTML的Perl脚本的bug。● Craig T. Snydal在德鲁大学(Drew University)的一门课上试验

这个课本,他提出了好几个有价值的建议和修订。● Ian Thomas和他的学生们使用这本书作为编程课程的教材。他们

第一个尝试使用本书后半部分的章节,并且提出了许多修正和建

议。● Keith Verheyden发来了第3章的一个修正。● Peter Winstanley让我们知道了第3章的拉丁文中一个长期存在的

错误。● Chris Wrobel修正了文件I/O和异常一章的代码错误。● Moshe Zadka对本书有不可估量的贡献。他编写了关于字典的一

章的第一版草稿,并在本书的早期阶段持续提供指导。● Christoph Zwerschke发来了几个修正和教学法的建议,并解释了

gleich和selbe的区别。● James Mayer发送给我们非常多的拼写错误,包括贡献者列表中

的两个错误。● Hayden McAfee发现了两个示例之间潜在的冲突。● Angel Arnal是翻译本书的西班牙语版本的国际团队的一员。他也

发现了英文版中的几个错误。● Tauhidul Hoque 和Lex Berezhny创建了第1章中的图表,并改进

了很多其他图表。● Dr. Michele Alzetta发现了第8章中的一个错误,并发来了一些有

趣的教学法评论,以及关于斐波那契数列和Old Maid的建议。● Andy Mitchell发现了第1章中的一个录入错误,以及第2章中一个

错误的示例。● Kalin Harvey对第7章的一个说明提供了建议,并发现了几个录入

错误。● Christopher P. Smith发现了几个录入错误,并帮助我们更新本书

到Python 2.2。● David Hutchins发现了前言中的一个错别字。● Gregor Lingl在奥地利维也纳的一个高中教授Python。他正在翻

译本书的德文版,并发现了第5章中的几个错误。● Julie Peters发现了前言中的一个错别字。● Florin Oprina发来一个makeTime的改进,printTime的一个修正,

以及发现的一个重要的录入错误。● D. J. Webre对第3章的一个说明提出了建议。● Ken在第8、9、11章中发现了好几个错误。● Ivo Wever在第5章发现一个录入错误,并对第3章中的一个说明

提出了建议。● Curtis Yanko对第2章中的一个描述提出了建议。● Ben Logan发来许多发现的录入错误,并发现了翻译HTML的问

题。● Jason Armstrong发现了第2章中一个漏掉的词。● Louis Cordier发现了第16章中有一个代码和文本不一致的地方。● Brian Cain在第2章和第3章中提出了几个描述的改进建议。● Rob Black发来了许多修正,包括一些针对Python 2.2的修改。● 巴黎中央理工大学的Jean-Philippe Rey发来了一些补丁,包括对

Python 2.2的更新,以及其他一些细心的改进。● 乔治华盛顿大学的Jason Mader提供了许多有用的建议和改正。● Jan Gundtofte-Bruun提醒我们“a error”应改为“an error”。● Abel David和Alexis Dinno 提醒我们“matrix”的复数形式是“matrices”而不是“matrixes”。这个错误在书中已经存在了多

年,但两个同姓的读者同一天报告了它。真的很奇怪。● Charles Thayer鼓励我们删除掉一些语句结尾的分号,并建议我

们理清“形参”和“实参”的使用。● Roger Sperberg指出了第3章的一个逻辑错误。● Sam Bull指出了第2章中一段令人困惑的描述。● Andrew Cheung指出了两处“定义前先使用”的错误。● C.Corey Capel发现了调试的第三定律中缺失的单词,以及第4章

的一个录入错误。● Alessandra 帮助我们理清了一些关于Turtle的困惑。● Wim Champagne在字典示例中发现一个错误。● Douglas Wright在弧度计算中发现了一个除法向下取整的错误。● Jared Spindor发现了一处句尾的无用词。● Lin Peiheng发来了许多很有用的建议。● Ray Hagtvedt发来了两处错误和一处不是那么错的错误。● Torsten Hübsch指出Sawmpy中的一处不一致。● Inga Petuhhov修正了第14章中的一个示例。● Arne Babenhauserheide发来了几个有用的修正。● Mark E. Casida非常善于发现重复的单词。● Scott Tyler填上了一个缺失的“that”,并发来了一堆修正。● Gordon Shephard发来了几个修正,每个都用单独的邮件。● Andrew Turner发现了第8章中的一个错误。● Adam Hobart修正了一个在弧度计算中除法向下取整的错误。● Daryl Hammond和Sarah Zimmerman指出我过早提出了math.pi。

并且Zim发现了一个录入错误。● George Sass在调试章节中发现了一个bug。● Brian Bingham建议了练习11-10。● Leah Engelbert-Fenton指出我用tuple做变量名称,这正违反了我

自己的建议。然后他发现了一堆录入错误以及一个“定义前先使

用”。● Joe Funke发现了一个录入错误。● Chao-chao Chen在斐波那契示例中发现了一个不一致处。● Jeff Paine知道space和spam的区别。● Lubos Pintes发来一个录入错误。● Gregg Lind和Abigail Heithoff建议了练习14-4。● Max Hailperin发来了许多修正和建议。Max是非凡的《具体抽象》(Concrete Abstractions)一书的作者之一。在读完本书之后你可

能会想要读那本书。● Chotipat Pornavalai在一个错误信息中发现了一个错误。● Stanislaw Antol寄来了一个很有用的建议列表。● Eric Pashman对第4章到第11章发来了许多修正。● Miguel Azevedo发现了一些录入错误。● Jianhua Liu发来了一长列修正。● Nick King发现了一个缺失单词。● Martin Zuther发来了一长列建议。● Adam Zimmerman发现了我举例的一个“实例”中的不一致处,

以及其他一些错误。● Ratnakar Tiwari建议加一个脚注说明什么是“退化”三角形。● Anurag Goel提出了is_abecedarian的另一个解答,并发来其他一

些修正。他还知道如何拼写Jane Austen。● Kelli Kratzer发现了一个录入错误。● Mark Griffiths指出了第3章中的一个令人困惑的示例。● Roydan Ongie发现了我的牛顿方法的一个错误。● Patryk Wolowiec帮我解决了一个HTML版本的问题。● Mark Chonofsky告诉我Python 3中的新关键字。● Russell Coleman帮我修正了几何错误。● Wei Huang发现了几处录入错误。● Karen Barber发现了本书中最古老的录入错误。● Nam Nguyen发现了一个录入错误,并指出我使用了装饰器模式

但并没有用它的名字。● Stéphane Morin发来了一些建议和修正。● Paul Stoop修改了一个uses_only中的录入错误。● Eric Bronner指出了关于操作符顺序的讨论中的一个困惑之处。● Alexandros Gezerlis提交的建议的数量和质量都设置了一个新的

标准。我们非常感谢他!● Gray Thomas知道哪边是左哪边是右。● Giovanni Escobar Sosa发来一长列的修正和建议。● Alix Etienne修正了一个URL。● Kuang He发现一个录入错误。● Daniel Neilson修正了一个关于操作符顺序的错误。● Will McGinnis指出polyline在两个地方定义的不同。● Swarup Sahoo发现了一个缺失的分号。● Frank Hecker指出一个练习不细致,并发现了几个坏链接。● Animesh B帮助我清理了一个令人困惑的示例。● Martin Caspersen发现了两处取整错误。● Gregor Ulm发来一些修正和建议。关于译者

赵普明 清华大学计算机科学技术专业毕业,长期从事Web应用、高性能服务器以及计算平台的开发。从2.3版本开始接触Python,至今已逾5年。工作中使用Python编写脚本程序,用于快速原型构建以及日志计算等日常作业;业余时,作为一个编程语言爱好者,对D、Kotlin、Clojure、Scala、Ruby等语言均有了解,但至今仍为Python独特的风格、简洁的设计而惊叹。O’Reilly Media, Inc.介绍

O’Reilly Media通过图书、杂志、在线服务、调查研究和会议等方式传播创新知识。自1978年开始,O’Reilly一直都是前沿发展的见证者和推动者。超级极客们正在开创着未来,而我们关注真正重要的技术趋势——通过放大那些“细微的信号”来刺激社会对新科技的应用。作为技术社区中活跃的参与者,O’Reilly的发展充满了对创新的倡导、创造和发扬光大。

O’Reilly为软件开发人员带来革命性的“动物书”;创建第一个商业网站(GNN);组织了影响深远的开放源代码峰会,以至于开源软件运动以此命名;创立了Make杂志,从而成为DIY革命的主要先锋;公司一如既往地通过多种形式缔结信息与人的纽带。O’Reilly的会议和峰会集聚了众多超级极客和高瞻远瞩的商业领袖,共同描绘出开创新产业的革命性思想。作为技术人士获取信息的选择,O’Reilly现在还将先锋专家的知识传递给普通的计算机用户。无论是通过书籍出版,在线服务或者面授课程,每一项O’Reilly的产品都反映了公司不可动摇的理念——信息是激发创新的力量。业界评论“O’Reilly Radar博客有口皆碑。”——Wired“O’Reilly凭借一系列(真希望当初我也想到了)非凡想法建立了数百万美元的业务。”——Business 2.0“O’Reilly Conference是聚集关键思想领袖的绝对典范。”——CRN“一本O’Reilly的书就代表一个有用、有前途、需要学习的主题。”——Irish Times“Tim是位特立独行的商人,他不光放眼于最长远、最广阔的视野并且切实地按照Yogi Berra的建议去做了:‘如果你在路上遇到岔路口,走小路(岔路)。’回顾过去Tim似乎每一次都选择了小路,而且有几次都是一闪即逝的机会,尽管大路也不错。”——Linux Journal第1章 程序之道

本书的目标是教会你像计算机科学家一样思考。这种思考方式综合了数学、工程学以及自然科学的一些最优秀的特性。计算机科学家和数学家类似,他们使用形式语言来描述理念(特别是计算);和工程师类似,他们设计产品,将元件组装成系统,评估选择不同的方案;和自然科学家类似,他们观察复杂系统的行为,形成科学假设,并检验其预测。

计算机科学家最重要的技能是问题解决。问题解决意味着发现问题,创造性地思考解决方案,以及清晰准确地表达解决方案的能力。事实证明,学习编程的过程,是训练问题解决能力的绝佳机会。这也是为什么本章标题是“程序之道”的原因。

一方面,你将学会编程,其本身就是一个非常有用的技能;另一方面,你可以使用编程作为工具,去达到更高的目标。随着本书的深入,那个目标会逐渐明晰。1.1 Python编程语言

你将学习的编程语言是Python。Python是一种高级语言。你可能还听说过其他的高级编程语言,比如C、C++、Perl和Java。

另外,也有低级语言,有时被称为“机器语言”或者“汇编语言”。一般地讲,计算机只能运行低级语言编写的程序。所以,使用高级语言编写的程序必须先处理过才能运行。额外的处理过程需要花费一点时间,这也是高级语言的一个小缺点。

但高级语言的优点是巨大的。首先,使用高级语言编写程序容易得多。高级语言的程序编写时耗时更少,程序更短,更容易阅读,且更容易保证正确。其次,高级语言是可移植的,这意味着仅需稍微修改或者不用修改,它们就可以在不同类型的计算机上运行。而低级语言的程序只能在一种计算机上运行,想用在不同的机器上,必须重写。

因为这些优点,几乎所有的程序都是用高级语言编写的。低级语言只在很稀少的特殊场景中使用。

有两种程序可以处理高级语言并将其转换为低级语言:解释器和编译器。解释器读入一段高级语言程序,并执行它。也就是说,它会按照程序的指令运行。它每次处理一小部分程序,交替读入代码行并进行运算。图1-1显示了解释器的结构。图1-1 解释器每次处理一小部分程序,交替读入代码行并进行运算

编译器读入程序,将其完整地编译为低级语言,才能运行。在这个场景中,高级语言的程序称为源代码,编译而成的程序称为目标代码或可执行代码。一旦程序编译完成,就不需要再进行编译,直接可以重复执行。图1-2显示了一个编译器的结构。图1-2 编译器将源代码编译为可以在硬件上执行的目标代码

Python被认为是解释语言,因为它是使用解释器执行的。解释执行的方式有两种:交互模式和脚本模式。在交互模式下,用户输入一段Python代码,解释器显示结果:>>> 1 + 12

这里>>>形的符号,用来提示用户解释器已经准备好接收代码输入。如果你输入1+1,解释器会答复2。

或者,你可以把代码保存到一个文件里,并使用解释器来执行文件内容。这样的文件称为脚本。依照惯例,Python脚本文件的后缀是.py。

要想执行脚本,需要将文件名通知给解释器。如果你有一个名为dinsdale.py的脚本,并且使用UNIX命令行,则可以输入python dinsdale.py。在其他开发环境中,脚本执行的细节会有所不同。你可以在Python网站http://python.org上查看自己使用的环境的具体说明。

使用交互模式对测试小段代码很方便,因为你可以输入代码并立即执行。但代码超过一定行数时,就应当保存到脚本文件中,以便于未来进行修改和执行。1.2 什么是程序

程序是指一组定义如何进行计算的指令的集合。这种计算可能是数学计算,例如解方程组或者查找多项式的根,但也可以是符号运算,例如搜索和替换文档中的文本,或者(很奇怪地)编译一个程序。

在不同的语言中,程序的细节有所不同,但几乎所有语言都会出现几类基本指令。● 输入:从键盘、文件或者其他设备中获取数据。● 输出:将数据显示到屏幕或者发送到文件或其他设备中。● 数学:进行基本数学操作,比如加法或乘法。● 条件执行:检查某种条件的状态,并执行相应的代码。● 重复:重复执行某种动作,往往在重复中有一些变化。

信不信由你,这差不多就是全部了。你所遇到过的所有程序,不论多么复杂,也都是由类似上面的这些指令组成的。所以我们可以把编程看做将大而复杂的任务分解为更小的子任务的过程,依此分解,直到任务简单得可以由上面的这些指令组合完成。

这看起来也许有些模糊,但我们会在讨论算法时再回到这个话题上来。1.3 什么是调试

程序是很容易出错的。因为某种古怪的原因,程序错误被称为bug,而查捕bug的过程称为调试(debugging)。

一个程序中可能出现3种类型的错误:语法错误、运行时错误和语义错误。对它们加以区分,可以更快地找到错误。1.4 语法错误

Python程序在语法正确的情况下才能运行;否则,解释器会显示一条错误信息。语法指的是程序的结构以及此结构的规则。比如,括号必须前后匹配,所以(1+2)是合法的,而8)就是一个语法错误。

在英语中,读者可以容忍大多数语法错误,因此我们可以阅读E. E. Cummings的诗,而不需要发出错误信息。但Python并不如此宽容。程序中只要出现一处语法错误,Python就会显示错误信息并退出,结果你的程序就无法运行了。在编程生涯的最初几周中,可能会需要花费大量时间来查找语法错误。但随着经验的增加,犯错减少,查找起来也会更快。1.5 运行时错误

第二类错误是运行时错误,这样称呼是因为这种错误只有程序运行后才会出现。这些错误也常被称为异常,因为它们常常表示某些异常的(而且不好的)事情发生了。

运行时错误在开头几章中的简单示例里很少会出现,所以可能要过一段时间,你才会遇到一个。1.6 语义错误

第三类错误是语义错误。如果你的程序中有一个语义错误,程序仍会成功运行,而不会产生任何错误信息,但是它不会执行正确的逻辑。它会做其他的事情。特别地,它会做的正是你告诉它所做的。

这里的问题在于你写出的代码和你想要写的代码并不一致。程序的意思(语义)是错的。查找语义错误会很麻烦,因为需要你反向查找,查看程序输出并尝试弄明白它到底做了什么。1.7 实验型调试

你将会掌握的一个最重要的技能就是调试。虽然调试可能较困惑,但它的确是编程活动中最动头脑、最有挑战、最有趣的部分。

在某种程度上,调试和刑侦工作很像。你会面对一些线索,而且必须推导出事情发生的过程,以及导致现场结果的事件。

调试也像是一种实验科学。一旦你猜出错误的可能原因,就可以修改程序,再运行一次。如果你猜对了,那么程序的运行结果会符合你的预测,这样就离正确的程序更近了一步。如果你猜错了,则需要重新思考。正如夏洛克 · 福尔摩斯所说的:“当你排除掉所有的可能性,那么剩下的,不管多么不可能,必定是真相。”(柯南 · 道尔《四签名》)

对某些人来说,编程和调试是同一件事。也就是说,编程正是不断调试修改直到程序达到设计目的的过程。这种想法的要旨是,你应该从一个能做某些事的程序开始,然后做一点点修改,并调试修改,如此迭代,以确保总是有一个可以运行的程序。

例如,Linux是包含了成千上万行代码的操作系统,但最开始只是Linus Torvalds 编写的用来研究Intel 80386芯片的简单程序。据Larry Greenfield所说,“Linus最早的一个程序是交替打印AAAA和BBBB。后来这些程序演化成了Linux。”(《Linux用户指南Beta版本1》)

后面的章节会介绍更多关于调试的技巧,以及其他的编程实践技巧。1.8 形式语言和自然语言

自然语言是指人们所说的语言,比如英语、西班牙语和法语。它们不是由人设计而来的(虽然人们会尝试加以语法限制),而是自然演化而来的。

形式语言则是人们设计出来用于特别用途的语言。比如,用来表达数学公式的符号标注系统,是一种特别擅于表示数字和符号的关系的形式语言。化学家则使用一种形式语言表达分子的化学结构。并且最重要的是:

编程语言是人们设计出来用来表达计算过程的形式语言。

形式语言倾向于对语法做出严格的限制。比如,3+3=6是语法正确的数学表达式,但3+ = 3$6则不是。HO是语法正确的化学方程,2而Zz则不是。2

语法规则有两种,分别适用于记号(token)和结构。记号是语言的基本元素,比如词、数字和化学元素。3+ = 3$6的一个问题就是$在数学表达式中(至少就我所知)不是合法记号。相似地, Zz不2合法,是因为并不存在缩写为Zz的化学元素。

第二种语法错误是关于语句的结构的。也就是说,记号所排列的方式。语句3+ = 3不合法,因为虽然+和=是合法记号,但不能将它们连续放置。相似地,在化学表达式里,下标数字应该出现在元素名称之后,而不是之前。练习1-1

写一个结构良好的英语句子,但其中包含非法的记号。再写一个句子,包含的记号都是合法的,但结构不合法。

当你阅读英语的句子或形式语言的语句时,你需要弄清句子的结构是什么 (虽然在自然语言中这个过程是下意识完成的)。这个过程称为语法分析。

比如,当你听到这句话:“硬币掉了。”会理解到,“硬币”是主语,而“掉了”是谓语。一旦解析完一句话,就能弄清楚它的涵义,或者说语义。假设你知道硬币是什么,并知道掉了意味着什么,就能够理解这句话的涵义。

虽然形式语言和自然语言有很多共同的特点——记号、结构、语法以及语义——它们也有一些区别。● 歧义性:自然语言充满了歧义,人们通过上下文线索和其他信息

来处理。形式语言通常设计为几乎或者完全没有歧义,意即不论

上下文环境如何,任何表达式都只有一个意义。● 冗余性:为了弥补歧义,减少误解,自然语言采用大量的冗余。

因此,它们常常冗长繁复。形式语言则不那么冗余,相对紧凑。● 字面直接性:自然语言充满了成语和比喻。比如有人说,“硬币

掉了”,并不一定是硬币,也不一定是有什么掉了(“The penny

dropped”在英语里的意思是:经过一段困惑后,突然发现之前

没有意识到的事情)。形式语言则严格按照它字面的意思表达涵

义。

说着自然语言长大的人(所有人)都需要经历一段挣扎才能适应形式语言。在某种意义上,形式语言和自然语言的区别,与诗词和散文的区别类似,而且更甚。● 诗词:字词的使用,既考虑到它们的音韵,也考虑到它们的意义,

而整首诗合起来表达某种意境或情绪反应。歧义不仅常见,而且

常常是刻意为之。● 散文:字词的意义更加重要,而且句子的结构也提供更多的意义。

散文比诗词更容易分析,但仍然有不少歧义。● 程序:计算机程序的意义不含歧义,而且直接如字面所指。并且

可以完全通过它的记号和结构理解其意义。

阅读代码(以及其他形式语言)时有如下建议。首先,记着形式语言的密度远远大于自然语言,所以阅读它们需要花费更多时间。其次,文法结构非常重要,所以直接自顶而下、从左至右的阅读顺序,并不一定是最好的。相反,试着学会在头脑中解析程序,辨别出记号并解析出结构。最后,细节很重要。在自然语言中常常可以忽略的小错误,如拼写或者标点错误,在形式语言中往往影响重大。1.9 第一个程序

依照传统,学习一门新语言,写的第一个程序都叫“Hello, World!”,因为这个程序所做的事情就是显示“Hello, World!”。在Python中,它是这个样子:print 'Hello, World!'

这是print语句的一个示例。print并不会真地往纸上打印文字,而是在屏幕上输出值。这个例子输出的结果是:Hello, World!

程序中的引号表示输出的文本的开始和结束;在输出结果中它们并不显示。

在Python 3中,这个语句的语法略有不同:print('Hello, World!')

括号表示print是一个函数。我们将在第3章中讨论函数的话题。

在本书后面的内容中,我会使用print语句。如果你是使用Python 3,需要自己转换。但除了这一处之外,很少有其他需要担心的区别了。1.10 调试

在电脑前阅读本书会是一个好主意,因为你可以边看边试验书中的示例。大部分的代码都可以使用交互模式运行。当然,如果你把代码存入到脚本中,可以方便尝试一些小修改。

每当你试验新的语言特性时,应当试着故意犯错。比如,在“Hello,World!”程序中,如果少写一个引号,会发生什么?如果两个引号都不写,会怎么样?如果把print拼写错了,会如何?

这种试验会帮助你记住所读的内容,也能帮你学会调试,因为这样能看到不同的错误信息代表着什么。现在故意犯错,总比今后在编码中意外出错好。

编程,特别是调试,有时候会引发强烈的情绪。如果你挣扎于一个困难的问题,可能会感觉到愤怒、沮丧以及窘迫。

有证据表明,人们会像对待人一样对待电脑。当电脑良好完成工作时,我们会把它们当作队友,而当它们难以控制、粗暴无礼的时候,我们会按照对待那些粗暴固执的人一样对待它们(《媒体等同:人们该如何像对待真人实景一样对待电脑、电视和新媒体》,Reeves和Nass著)。

对这些反应行为有所准备,可能会帮助你更好地对待电脑。一种方法是把它当作你的雇员,它有一定的长处,比如速度和精度,也有特定的弱点,比如没有同情心,以及无法顾全大局。

你的任务是做一个好经理:设法扬长避短。并找到方法控制你的情绪去面对问题,而不是让你的反应影响工作的效率。

学习调试可能会带来挫折感,但它是一个有价值的技能,并在编程之外还有很多用途。每章的结尾处都有一节类似于本节的关于调试技巧的讨论。希望它们能带来帮助!1.11 术语表

问题解决(problem solving):总结问题、寻找解决方案以及表达解决方案的过程。

高级语言(high-level language):设计来方便人们读写的编程语言,比如Python。

低级语言(low-level language):设计来方便计算机执行的编程语言,也被称为“机器语言”或“汇编语言”。

可移植性(portability):程序的一种属性:可以在多种类型的计算机上运行。

解释(interpret):按照一行一行解释翻译的方式来执行高级语言编写的程序。

编译(compile):一次性将一个高级语言编写的程序翻译为低级语言程序,之后可以单独运行。

源代码(source code):使用高级语言编写的程序,在编译之前称为源代码。

目标代码(object code):编译器输出的程序。

可执行文件(executable):目标代码的另一个名字,表示它可以直接被执行。

提示符(prompt):解释器显示出来的文字,用来表示它准备好接收用户新的输入。

脚本(script):保存在文件中的程序(用于被解释器解释执行)。

交互模式(interactive mode):使用Python解释器的一种方式。在解释器的提示处输入命令和表达式。

脚本模式(script mode):使用Python解释器的另一种方式。读取并执行一个脚本文件中的代码。

程序(program):一系列代码指令的集合,指定一种运算。

算法(algorithm):解决某一类问题的通用运算流程。

bug:程序中的一个错误。

调试(debugging):发现和解决程序中出现的3类错误的过程。

语法(syntax):程序的结构。

语法错误(syntax error):程序中的一种错误,导致它无法进行语法解析(因此也无法被解释器执行)。

异常(exception):程序运行中发现的错误。

语义(semantics):程序表达的意义。

语义错误(semantic error):程序中的一种错误,导致它运行所做的事情和程序员设想的不同。

自然语言(natural language):自然演化而来的人们所说的语言。

形式语言(formal language):人们设计来用于某些特定目的的语言,例如表达数学概念或者计算机程序。所有的编程语言都属于形式语言。

记号(token):程序的语法结构的最基本单位,类似于自然语言中的词。

语法分析(parse):检查程序并分析其语法结构。

print语句(print statement):一个指令,可以通知Python解释器在屏幕上输出一个值。1.12 练习练习1-2

使用浏览器访问Python的网站http://python.org。这个页面包含了Python的相关信息,以及到Python相关页面的链接,并且可以在上面搜索Python的文档。练习1-3

启动Python解释器,输入help()来启动在线帮助功能。或者,可以输入help('print')获取print语句的相关文档。

如果这个例子不能运行,可能是由于需要安装额外的Python文档模块,或者需要设置环境变量;详细情况依赖于你使用的操作系统和Python版本。练习1-4

启动Python解释器,把它当作计算器使用。Python的数学运算语法和标准的数学标记法一致。比如,和预料一样,符号+、−和/分别表示加法、减法和除法。乘法的符号是*。

如果你用43分30秒跑完10公里,那么你平均跑1英里需要多长时间?平均速度是多少英里每小时?(提示:1英里相当于1.61公里。)第2章 变量、表达式和语句2.1 值和类型

值(value)是程序操作的最基本的东西。比如一个字符或者数字。至今我们见过这些值:1,2,以及'Hello,World!'。

这些值属于不同的类型:2是一个整数,而'Hello,World!'是一个字符串,这么称呼是因为它包含了一“串”字符。你(和解释器)可以通过包含它们的引号确定字符串。

如果不确认一个值的类型,解释器可以告诉你:>>> type('Hello, World!')>>> type(17)

不足为奇,字符串的类型是'str',而整数属于类型'int'。但并不明显的是,包含一个小数点的数,属于称为浮点型('float')的类型,因为这些数字的表达形式中有一个浮点(floating-point)。>>> type(3.2)

那么'17'和'3.2'这样的值呢?它们看起来像是数字,但又使用字符串常用的引号包含。>>> type('17')>>> type('3.2')

它们是字符串。

当输入一个很大的数字时,你可能会忍不住想在数字中间加上逗号,就像1,000,000这样。在Python中这并不是合法的数字,但它是合法的表达式:>>> 1,000,000(1, 0, 0)

当然,这和我们设想的完全不同!Python把1,000,000解释成一个用逗号分隔的数字序列。这是我们遇到的第一个语义错误:代码并不报错,可以正常运行,但是它做的并不是“正确”的事情。2.2 变量

编程语言最强大的功能之一是操纵变量的能力。变量是指向一个值的名称。

赋值语句可以建立新的变量,并给它们赋值:>>> message = 'And now for somthing completely different'>>> n = 17>>> pi = 3.1415926535897932

这个例子有3个赋值。第一个将一个字符串赋给叫做message的变量;第二个将17赋值给n;第三个将π的(近似)值赋给变量pi。

在纸上表达变量的一个常见方式是写下名称,并用箭头指向其值。这种图称为状态图,因为它显示了每个变量所在的状态 (请将它看作变量的心理状态)。图2-1显示了前面例子的状态图。图2-1 状态图

变量的类型和它所指向的值类型相同。>>> type(message)>>> type(n)>>> type(pi)练习2-1

如果输入一个以0开头的整数,则会得到一个令人困惑的错误:>>> zipcode = 02492         ^SyntaxError: invalid token

其他的数字又似乎可以正常运行,但结果也很奇怪:>>> zipcode = 02132>>> zipcode1114

你能想出是怎么回事吗?提示:显示01、010、0100和01000的值。2.3 变量名称和关键字

程序员常常选择有意义的名称作为变量名——以此标记变量的用途。

变量名可以任意长短。它可以包含字母和数字,但必须以一个字母开头。使用大写字母是合法的,但变量名使用小写字母开头是个好主意(后面你会看到为何如此)。

下划线“_”可以出现在变量名称中。它经常出现在由多个词组成的变量名中,如my_name或airspeed_of_unladen_swallow。

如果你给变量取非法的名称,会得到一个语法错误:>>> 76trombones = 'big parade'SyntaxError: invalid syntax>>> more@ = 1000000SyntaxError: invalid syntax>>> class = 'Advanced Theoretical Zymurgy'SyntaxError: invalid syntax

76trombones非法,因为它不是以字母开头。more@非法,是因为它包含了一个非法字符@。但class有什么问题?

原因是class是Python的一个关键字。解释器通过关键字来识别程序的结构,并且它们不能用来作为变量名称。

Python 2共有31个关键字:anddelfromnotwhileasglobalorelifwithpassyieldassertelseifexceptimportprintbreakexecclassinraisefinallycontinueisreturntrydefforlambda

在Python 3中,exec不再是关键字,但nonlocal是一个新的关键字。

你可能需要随身携带这个列表。如果编译器抱怨你的变量名称而不知为何,先查看一下是不是在这个列表中。2.4 操作符和操作对象

操作符是一些特殊符号,表示像加法乘法这样的运算。操作符所操作的值,称为操作对象。

操作符+、−、*、/以及**分别进行加法、减法、乘法、除法以及乘方操作。下面是示例:20+32  hour-1  hour*60+minute  minute/60  5**2  (5+9)*(15-7)

在其他语言中,乘方操作用^表示,但是在Python中^用来表示按位异或(XOR)的操作。本书中不会涉及按位操作,但你可以在http://wiki.python.org/moin/BitwiseOperators中读到相关信息。

在Python 2中,除法可能和你想象的不一样:>>> minute = 59>>> minute/600

minute的值是59,而在传统的算术中,59除以60是0.98333,而不是0。这其中的差别是因为Python使用的是舍去式除法(floor division)。当两个操作对象都是整数时,结果也是个整数。舍去式除法会舍去小数部分,所以在这个例子中,结果被取整成为0。

在Python 3中,这个除法的结果是浮点数类型。而使用一个新的//操作符用来表示舍去式除法。

如果两个操作数中有一个是浮点数,那么Python就会进行浮点除法,结果也是浮点数:>>> minute/60.00.983333333333333282.5 表达式和语句

表达式是值、变量和操作符的组合。单独一个值也被看作一个表达式,单独的变量也是如此。所以下面都是合法的表达式(假设变量x已经被赋值):17xx + 17

语句是Python解释器能运行的一个代码单元。我们已经看到了两种语句:print语句和赋值语句。

从技术上说表达式也是一个语句,但把它们看作不同的事物区分开也许更简单。重要的区别是,表达式有值,而语句没有。2.6 交互模式和脚本模式

使用解释型语言的好处之一在于你可以在交互模式中尝试一些代码片段,再将它们写入到脚本中。但交互模式和脚本模式也有一些不同,有时候会带来困惑。

比如,如果使用Python作为计算器,你可能会输入:>>> miles = 26.2>>> miles * 1.6142.182

第一行给变量miles赋值,但没有可见的效果。第二行是一个表达式,所以解释器对其进行求值,并显示结果。于是我们知道马拉松的长度大概是42公里。

但如果将上面同样的代码写入到脚本中并运行,则得不到任何输出。在脚本模式中,一个单独的表达式,也是没有可见效果的。Python实际上会对表达式进行求值,但不会显示其结果。除非你叫它这么做:miles = 26.2print miles * 1.61

这种现象一开始可能会让人迷惑。

脚本通常包含一系列的语句。如果语句超过一行,那么会随着语句执行的顺序一行行显示结果。

比如,脚本print 1x = 2print x

产生如下结果12

赋值语句不会产生任何输出。练习2-2

在Python解释器中输入下面的语句,看它们做了什么:5x = 5 x + 1

现在把同样的语句存入到一个脚本文件并运行。输出是什么?修改脚本,将所有的表达式都转换成print语句,再运行一遍。2.7 操作顺序

当一个表达式中出现多个操作符时,求值的顺序依赖于优先级规则。对数学操作符,Python遵守数学的传统规则。缩略词PEMDAS可以帮助记忆这些规则:● 括号(P,Parentheses)拥有最高的优先级,并可以用来强制表

达式按照你需要的顺序进行求值。因为括号中的表达式会先执行,

所以2*(3-1)的结果是4,而(1+1)**(5-2)的结果是8。你也可以利用

括号使得表达式更加易读,就像(minute*100)/60这样,即使这里

增加括号并不会改变结果。● 乘方(E,Exponentiation)操作拥有次高的优先级,所以2**1+1

的结果是3,而不是4,而且3*1**3的结果是3,而不是27。● 乘法(M,Multiplication)和除法(D,Division)优先级相同,

并且高于亦有相同优先级的加法(A,Addition)和减法(S,

Substraction)。所以2*3-1是5,而不是4,并且6+4/2是8,而不

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载