Think Python/编程之道
本书的目的是教你像计算机科学家一样思考。这种思维方式结合了数学、工程和自然科学中一些最好的特点。像数学家一样,计算机科学家使用形式语言来表示思想(特别是计算)。像工程师一样,他们设计事物,将组件组装成系统,并在替代方案之间权衡利弊。像科学家一样,他们观察复杂系统的行为,形成假设,并检验预测。
对于计算机科学家来说,最重要的技能是 **解决问题**。解决问题意味着能够制定问题,创造性地思考解决方案,并清晰准确地表达解决方案。事实证明,学习编程的过程是练习解决问题技能的绝佳机会。这就是本章被称为“编程之道”的原因。
在一个层面上,你将学习编程,这本身就是一项有用的技能。在另一个层面上,你将使用编程作为一种手段来达到目的。随着我们的学习,这个目的将变得越来越清晰。
你将学习的编程语言是 Python。Python 是 **高级语言** 的一个例子;你可能听说过的其他高级语言包括 C、C++、Perl 和 Java。
还有 **低级语言**,有时被称为“机器语言”或“汇编语言”。简单来说,计算机只能执行用低级语言编写的程序。因此,用高级语言编写的程序必须经过处理才能运行。这种额外的处理需要一些时间,这是高级语言的一个小缺点。
优点是巨大的。首先,用高级语言编程要容易得多。用高级语言编写的程序编写时间更短,代码更简洁,更易于阅读,并且更有可能正确。其次,高级语言是 **可移植的**,这意味着它们可以在不同的计算机上运行,几乎不需要修改。低级程序只能在一种类型的计算机上运行,必须重新编写才能在其他计算机上运行。
由于这些优点,几乎所有程序都是用高级语言编写的。低级语言只用于少数专业应用。
两种程序将高级语言处理成低级语言:**解释器** 和 **编译器**。解释器读取高级程序并执行它,也就是说它按照程序的指示执行操作。它一次处理一小段程序,交替读取行并执行计算。
编译器读取程序并在程序开始运行之前将其完全翻译。在这种情况下,高级程序被称为 **源代码**,翻译后的程序被称为 **目标代码** 或 **可执行文件**。一旦程序被编译,你就可以重复执行它,而无需进一步翻译。
Python 被认为是一种解释型语言,因为 Python 程序是由解释器执行的。使用解释器有两种方法:**交互模式** 和 **脚本模式**。在交互模式下,你输入 Python 程序,解释器打印结果
>>> 1 + 1
2
尖括号,>>>
,是解释器用来指示它已准备好的 **提示符**。如果你输入1 + 1,然后按下回车键,解释器会回复2.
或者,你可以将代码存储在一个文件中,并使用解释器执行该文件的内容,该文件被称为 **脚本**。按照惯例,Python 脚本的名称以.py.
结尾。要执行脚本,你必须告诉解释器文件名。在 UNIX 命令窗口中,你会输入python dinsdale.py。在其他开发环境中,执行脚本的细节有所不同。你可以在 Python 网站上找到你的环境的说明python.org.
在交互模式下工作对于测试小段代码很方便,因为你可以立即输入并执行它们。但是,对于超过几行的代码,你应该将代码保存为脚本,以便你可以在将来修改和执行它。
**程序** 是一个指令序列,它指定了如何执行计算。计算可能是数学的,比如解方程组或求多项式的根,但也可能是符号计算,比如在文档中搜索和替换文本或(奇怪的是)编译程序。
不同的语言细节不同,但以下几种基本指令几乎出现在所有语言中
- 输入
- 从键盘、文件或其他设备获取数据。
- 输出
- 在屏幕上显示数据或将数据发送到文件或其他设备。
- 数学
- 执行基本数学运算,如加法和乘法。
- 条件执行
- 检查某些条件并执行相应的语句序列。
- 重复
- 重复执行某些操作,通常带有某种变化。
信不信由你,这就是全部。你用过的每一个程序,无论多么复杂,都是由看起来非常像这些指令的指令组成的。因此,你可以将编程视为将一个大型复杂的任务分解成越来越小的子任务的过程,直到子任务足够简单,可以用这些基本指令之一来执行。
这可能有点模糊,但当我们讨论 **算法** 时,我们将回到这个话题。
编程容易出错。出于奇特的原因,编程错误被称为 **bug**,追踪它们的过程被称为 **调试**。
程序中可能出现三种类型的错误:语法错误、运行时错误和语义错误。为了更快地追踪它们,区分它们是有用的。
Python 只能在语法正确的情况下执行程序;否则,解释器会显示一条错误消息。**语法** 指的是程序的结构以及关于该结构的规则。例如,括号必须成对出现,所以(1 + 2)是合法的,但是8)是一个 **语法错误**。
在英语中,读者可以容忍大多数语法错误,这就是为什么我们可以阅读 E. E. Cummings 的诗歌而不会出现错误消息的原因。Python 并没有那么宽容。如果你的程序中存在任何一个语法错误,Python 就会显示一条错误消息并退出,你将无法运行你的程序。在你编程生涯的头几个星期里,你可能要花很多时间来追踪语法错误。随着经验的积累,你会犯更少的错误,并且找到它们的速度也会更快。
第二种类型的错误是运行时错误,之所以这样称呼,是因为这种错误直到程序开始运行后才会出现。这些错误也被称为 **异常**,因为它们通常表明发生了异常(并且糟糕的)情况。
在本章中你将看到的简单程序中,运行时错误很少见,所以你可能要等一段时间才会遇到一个。
第三种错误是语义错误。如果你的程序存在语义错误,它将成功运行,计算机不会生成任何错误消息,但它不会做正确的事。它会做其他事情。具体来说,它会按照你告诉它做的去做。
问题在于你编写的程序不是你想要的程序。程序的含义(语义)是错误的。识别语义错误可能很棘手,因为它需要你通过查看程序的输出并试图弄清楚它在做什么来反向工作。
你将获得的最重要的技能之一是调试。尽管调试可能令人沮丧,但它却是编程中最具智力丰富、挑战性和趣味性的部分之一。
在某些方面,调试就像侦探工作。你面临着线索,你必须推断导致你看到的结果的过程和事件。
调试也像实验科学。一旦你对错误的原因有了想法,你就修改你的程序并再次尝试。如果你的假设是正确的,那么你就可以预测修改的结果,并且你离一个可运行的程序更近了一步。如果你的假设是错误的,你必须想出一个新的假设。正如夏洛克·福尔摩斯指出的那样,“当你排除了不可能的事情时,无论剩下什么,无论多么不可能,都必须是真相。”(阿瑟·柯南·道尔,《四签名》)
对某些人来说,编程和调试是一回事。也就是说,编程是逐步调试程序直到它执行你想要的操作的过程。这个想法是你应该从一个做一些事情的程序开始,并进行小的修改,在进行过程中调试它们,这样你始终拥有一个可运行的程序。
例如,Linux 是一个包含数千行代码的操作系统,但它最初是一个简单的程序,Linus Torvalds 用它来探索 Intel 80386 芯片。据拉里·格林菲尔德所说,“Linus 早期的项目之一是一个在打印 AAAA 和 BBBB 之间切换的程序。后来发展成了 Linux。”(《Linux 用户指南》Beta 版 1)。
后面的章节将对调试和其他编程实践提出更多建议。
自然语言是人们使用的语言,例如英语、西班牙语和法语。它们不是由人设计的(尽管人们试图对它们施加一些秩序);它们是自然演变的。
形式语言是专门为特定应用而设计的人类语言。例如,数学家使用的符号是一个形式语言,特别擅长表示数字和符号之间的关系。化学家使用形式语言来表示分子的化学结构。最重要的是
编程语言是专门为表达计算而设计的人类语言。
形式语言往往对语法有严格的规则。例如,3 + 3 = 6 是一个语法正确的数学表达式,但 3 + = 3 $6 不是。H2O 是一个语法正确的化学式,但2Zz 不是。
语法规则分为两种,分别与符号和结构有关。符号是语言的基本元素,例如单词、数字和化学元素。3 + = 3 $6 的问题之一是$
在数学中不是合法的符号(至少据我所知)。类似地,2Zz 不合法,因为没有元素的缩写是Zz。
第二种语法错误与语句的结构有关;也就是说,符号的排列方式。语句 3 + = 3 $6是非法的,因为即使 + 和 = 是合法的符号,你也不可能一个接一个地使用它们。同样,在化学式中,下标在元素名称之后,而不是之前。
当你阅读英语中的句子或形式语言中的语句时,你必须弄清楚句子的结构(尽管在自然语言中你是下意识地这样做的)。这个过程称为解析。
例如,当你听到这句话“The penny dropped”时,你明白“the penny”是主语,“dropped”是谓语。一旦你解析了一个句子,你就可以弄清楚它的意思,或者句子的语义。假设你知道便士是什么以及它掉下来的意思,你将理解这个句子的普遍含义。
尽管形式语言和自然语言有许多共同点——符号、结构、语法和语义——但也有一些差异
- 歧义
- 自然语言充满了歧义,人们通过使用上下文线索和其他信息来处理它们。形式语言被设计为几乎或完全无歧义的,这意味着任何语句都只有一个含义,与上下文无关。
- 冗余
- 为了弥补歧义并减少误解,自然语言采用了大量的冗余。因此,它们通常很冗长。形式语言的冗余度更低,更简洁。
- 字面意思
- 自然语言充满了习语和隐喻。如果我说,“The penny dropped”,可能没有便士,也没有东西掉下来。[1] 形式语言的意思完全是字面意思。
从小就说自然语言的人——每个人——通常很难适应形式语言。在某些方面,形式语言和自然语言之间的差异就像诗歌和散文之间的差异,但程度更甚
- 诗歌
- 词语的音韵和意义都得到了利用,整首诗共同创造了一种效果或情感反应。歧义不仅常见,而且通常是故意的。
- 散文
- 词语的字面意思更为重要,结构贡献了更多意义。散文比诗歌更容易分析,但仍然经常出现歧义。
- 程序
- 计算机程序的含义是明确的和字面意思的,可以通过对符号和结构的分析完全理解。
以下是一些关于阅读程序(和其他形式语言)的建议。首先,请记住形式语言比自然语言密集得多,因此阅读它们需要更长的时间。此外,结构非常重要,因此通常从上到下、从左到右阅读不是一个好主意。相反,学会在脑海中解析程序,识别符号并解释结构。最后,细节很重要。拼写和标点符号上的小错误,你在自然语言中可以逃避,但在形式语言中却会有很大的区别。
传统上,你在新语言中编写的第一个程序称为“Hello, World!”,因为它只做一件事,就是显示“Hello, World!”在 Python 中,它看起来像这样
print('Hello, World!')
这是一个打印语句的示例,[2]它实际上并没有在纸上打印任何东西。它在屏幕上显示一个值。在这种情况下,结果是单词
Hello, World!
程序中的引号标记要显示的文本的开头和结尾;它们不会出现在结果中。
有些人根据“Hello, World!”程序的简单性来判断编程语言的质量。根据这个标准,Python 表现得尽可能好。
最好在电脑前阅读这本书,这样你就可以边读边尝试示例。你可以在交互模式下运行大多数示例,但如果你将代码放入脚本中,则更容易尝试各种变体。
每当你尝试一个新功能时,你都应该尝试犯错。例如,在“Hello, world!”程序中,如果你省略了一个引号会发生什么?如果你省略了两个会发生什么?如果你拼错了print呢?
这种实验可以帮助你记住你读到的内容;它还有助于调试,因为你将了解错误消息的含义。现在有意识地犯错误比以后无意地犯错误要好。
编程,尤其是调试,有时会激发出强烈的感情。如果你正在努力解决一个难以解决的错误,你可能会感到愤怒、沮丧或尴尬。
有证据表明,人们会自然地将计算机视为真人,并做出相应的反应。[3] 当计算机运作良好时,我们将其视为团队成员;当它们固执或无礼时,我们也会像对待无礼、固执的人一样回应它们。
提前做好应对这些反应的准备,可能会帮助你处理它们。一种方法是将计算机想象成一个员工,它拥有某些优势,比如速度和精度,但也有一些弱点,比如缺乏同理心和无法理解全局。
你的任务是成为一名优秀的管理者:找到方法来利用这些优势,并减轻这些弱点的负面影响。同时,也要找到方法利用自己的情绪来解决问题,但不要让你的反应影响你有效工作的能力。
学习调试可能会让人感到沮丧,但这项技能非常宝贵,它在很多活动中都有用,不仅仅是编程。在每一章的结尾,都会有一个类似于本节的调试部分,其中包含我对调试的一些想法。我希望它们能有所帮助!
- 问题解决
- 制定问题、寻找解决方案并表达解决方案的过程。
- 高级语言
- 像 Python 这样的编程语言,旨在让人类易于阅读和编写。
- 低级语言
- 旨在让计算机易于执行的编程语言;也称为“机器语言”或“汇编语言”。
- 可移植性
- 程序可以在多种类型的计算机上运行的特性。
- 解释
- 通过逐行翻译来执行高级语言编写的程序。
- 编译
- 将用高级语言编写的程序一次性翻译成低级语言,以备将来执行。
- 源代码
- 编译之前的高级语言程序。
- 目标代码
- 编译器在翻译程序后输出的结果。
- 可执行文件
- 已准备好执行的目标代码的另一种名称。
- 提示符
- 解释器显示的字符,表示它已准备好接收用户的输入。
- 脚本
- 存储在文件中的程序(通常是将被解释的程序)。
- 交互模式
- 在提示符下输入命令和表达式以使用 Python 解释器的一种方式。
- 脚本模式
- 使用 Python 解释器读取和执行脚本中语句的一种方式。
- 程序
- 指定计算的一组指令。
- 算法
- 解决一类问题的一般过程。
- 错误
- 程序中的错误。
- 调试
- 查找和移除三种编程错误中的任何一种的过程。
- 语法
- 程序的结构。
- 语法错误
- 程序中的错误,导致程序无法解析(因此也无法解释)。
- 异常
- 程序运行时检测到的错误。
- 语义
- 程序的含义。
- 语义错误
- 程序中的错误,导致程序执行的操作与程序员的意图不符。
- 自然语言
- 人们自然演化而来的任何一种语言。
- 形式语言
- 人们为特定目的而设计的任何一种语言,例如表示数学概念或计算机程序;所有编程语言都是形式语言。
- 词法单元
- 程序语法结构的基本元素之一,类似于自然语言中的单词。
- 解析
- 检查程序并分析其语法结构。
- 打印语句
- 导致 Python 解释器在屏幕上显示值的指令。
使用网络浏览器访问 Python 网站:https://www.pythonlang.cn/。此页面包含有关 Python 的信息以及指向 Python 相关页面的链接,它还允许你搜索 Python 文档。例如,如果你在搜索窗口中输入print,出现的第一个链接就是print语句的文档。目前,你可能无法理解所有内容,但了解它的位置很重要。
启动 Python 解释器并输入 'help()' 以启动在线帮助工具。或者,你可以输入 help('print')
获取有关 'print' 语句的信息。
如果此示例不起作用,你可能需要安装额外的 Python 文档或设置环境变量;具体细节取决于你的操作系统和 Python 版本。
启动 Python 解释器并将其用作计算器。Python 的数学运算语法与标准数学符号几乎相同。例如,符号 '+'、'-' 和 '/' 分别表示加法、减法和除法,正如你所预期的那样。乘法的符号是 '*'。
如果你跑了 10 公里的比赛,用了 43 分 30 秒,你的平均每英里时间是多少?你的平均时速是多少?(提示:1 英里约等于 1.61 公里)。