你为什么应该学习编写程序?
编写程序(或编程)是一项非常有创造力和价值的活动。你可以出于多种原因编写程序,从谋生、解决困难的数据分析问题、从中获得乐趣,再到帮助他人解决问题。本书假设每个人都需要知道如何编程,并且一旦你知道如何编程,你就会弄清楚你想用你新掌握的技能来做什么。
在日常生活中,我们被从笔记本电脑到手机等各种计算机所包围。我们可以将这些计算机视为我们的“个人助理”,它们可以代表我们处理许多事情。我们现今计算机中的硬件基本上就是为了不断地问我们一个问题:“接下来你想让我做什么?”
个人数字助理
程序员为硬件添加了操作系统和一系列应用程序,最终我们得到了一个相当有用的个人数字助理,能够帮助我们做许多不同的事情。
我们的计算机速度快,拥有海量内存,如果我们懂得与计算机交流的语言,告诉它我们希望它“接下来做什么”,它们就能为我们提供巨大的帮助。如果我们掌握了这门语言,我们就可以让计算机代替我们执行重复性的任务。有趣的是,计算机最擅长做的事情,往往正是我们人类觉得枯燥乏味、令人麻木的事情。
例如,看看本章的前三段,告诉我最常用的词是什么,以及这个词用了多少次。虽然你能在几秒钟内阅读并理解这些词语,但数数几乎是痛苦的,因为这不是人类大脑设计用来解决的问题。对计算机来说,情况正好相反,从一张纸上阅读和理解文本对计算机来说很难,但是数词频并告诉你最常用的词用了多少次,对计算机来说却非常容易:
python words.py
Enter file: words.txt
to 16
我们的“个人信息分析助手”迅速告诉我们,“to”这个词在本章的前三段中被使用了十六次。
正是因为计算机擅长人类不擅长的事情,所以你需要熟练掌握“计算机语言”。一旦你学会了这门新语言,你就可以将单调的任务委托给你的伙伴(计算机),从而为你自己留出更多时间去做那些你特别擅长做的事情。你为这种伙伴关系带来了创造力、直觉和独创性。
创造力与动机
虽然本书并非面向专业程序员,但专业编程无论在经济上还是个人层面,都可以是一份非常有价值的工作。为他人构建有用、优雅且巧妙的程序是一项非常有创造力的活动。你的计算机或个人数字助理(PDA)通常包含来自许多不同程序员团队的许多不同程序,每个程序都在争夺你的注意力和兴趣。他们尽最大努力满足你的需求,并在此过程中为你提供出色的用户体验。在某些情况下,当你选择某个软件时,程序员会因为你的选择而直接获得报酬。
如果我们将程序视为程序员群体的创造性成果,也许下图是我们的 PDA 一个更合理的版本:
程序员与你对话
我们现在的主要动机不是为了赚钱或取悦最终用户,而是为了让我们能够更高效地处理我们生活中将遇到的数据和信息。刚开始时,你既是程序员,也是你自己程序的最终用户。随着你作为程序员的技能增长,编程对你来说变得更有创造性,你的想法可能会转向为他人开发程序。
计算机硬件架构
在我们开始学习用于指导计算机开发软件的语言之前,我们需要对计算机的构造有一点了解。如果你拆开你的计算机或手机,深入观察内部,你会发现以下部件:
硬件架构
这些部件的高级定义如下:
中央处理器(或 CPU)是计算机中专注于“下一步做什么?”的部分。如果你的计算机标称 3.0 千兆赫兹,这意味着 CPU 每秒会问三十亿次“下一步做什么?”。你将需要学会快速地与它沟通才能跟上 CPU 的步伐。
主内存用于存储 CPU 急需的信息。主内存的速度几乎和 CPU 一样快。但是当计算机断电时,存储在主内存中的信息会消失。
辅助内存也用于存储信息,但它比主内存慢得多。辅助内存的优点是即使在没有电源的情况下也能存储信息。辅助内存的例子包括磁盘驱动器或闪存(常见于 U 盘和便携式音乐播放器)。
输入和输出设备就是我们的屏幕、键盘、鼠标、麦克风、扬声器、触摸板等。它们是我们与计算机交互的所有方式。
如今,大多数计算机还具有网络连接,用于通过网络检索信息。我们可以将网络视为一个非常缓慢的数据存储和检索场所,而且可能并不总是“可用”。因此,在某种意义上,网络是一种速度更慢且有时不太可靠的辅助内存形式。
虽然这些组件如何工作的大部分细节最好留给计算机制造商,但掌握一些术语有助于我们在编写程序时讨论这些不同的部分。
作为程序员,你的工作是使用和协调所有这些资源来解决你需要解决的问题,并分析你从解决方案中获得的数据。作为程序员,你将主要与 CPU “对话”,告诉它接下来要做什么。有时你会告诉 CPU 使用主内存、辅助内存、网络或输入/输出设备。
你在哪里?
你需要成为回答 CPU “下一步做什么?”这个问题的人。但是把你缩小到 5 毫米高,然后塞进计算机里,只为了让你每秒发出三十亿次命令,这会非常不舒服。所以,你必须提前写下你的指令。我们称这些存储的指令为程序,而将这些指令写下来并确保指令正确的行为叫做编程。
理解编程
在本书的其余部分,我们将努力将你培养成一个精通编程艺术的人。最终你将成为一名程序员——也许不是专业程序员,但至少你将拥有审视一个数据/信息分析问题并开发一个程序来解决该问题的技能。
从某种意义上说,你需要两种技能才能成为一名程序员:
首先,你需要了解编程语言(Python)——你需要知道词汇和语法。你需要能正确拼写这门新语言中的单词,并知道如何构造合乎语法的“句子”。
其次,你需要会“讲故事”。在写故事时,你会组合词语和句子来向读者传达一个想法。构建故事需要技巧和艺术,而写作技巧的提升需要通过一些写作和获得反馈来完成。在编程中,我们的程序就是“故事”,而你试图解决的问题就是“想法”。
一旦你学会了一种编程语言,比如 Python,你会发现学习第二门编程语言,如 JavaScript 或 C++,要容易得多。新的编程语言有非常不同的词汇和语法,但解决问题的技能在所有编程语言中都是通用的。
你会很快学会 Python 的“词汇”和“句子”。你需要更长的时间才能编写出一个连贯的程序来解决一个全新的问题。我们教编程很像我们教写作。我们从阅读和解释程序开始,然后编写简单的程序,随着时间的推移再编写越来越复杂的程序。在某个时刻,你会“灵感迸发”,自己看到模式,并能更自然地看待一个问题并编写一个解决该问题的程序。一旦你达到那个程度,编程就变成了一个非常愉快和富有创造性的过程。
我们从 Python 程序的词汇和结构开始。请耐心等待,因为这些简单的例子可能会让你想起你第一次开始阅读的时候。
词汇和语句
与人类语言不同,Python 的词汇量实际上相当小。我们称这个“词汇表”为“保留字”或“关键字”。这些词对 Python 有着非常特殊的意义。当 Python 在 Python 程序中看到这些词时,它们对 Python 只有一个意义。稍后在你编写程序时,你会创造出对你有意义的你自己的词,称为变量。你在为变量选择名称时有很大的自由度,但不能使用 Python 的任何保留字作为变量名。
当我们训练狗时,我们会使用特殊的词,如“坐下”、“待着”和“捡回来”。当你和狗说话而不使用任何保留字时,它们只会用困惑的表情看着你,直到你说出一个保留字。例如,如果你说,“我希望更多人走路来改善他们的整体健康”,大多数狗可能听到的是,“吧啦吧啦吧啦 走 吧啦吧啦吧啦吧啦。”这是因为“走”是狗语中的保留字。许多人可能认为人与猫之间的语言没有保留字 1。
人类与 Python 对话所用语言中的保留字包括以下这些:
False await else import pass
None break except in raise
True class finally is return
and continue for lambda try
as def from nonlocal while
assert del global not with
async elif if or yield
就是这些了,而且和狗不同,Python 已经完全训练好了。当你说 “try” 时,Python 每次都会尝试,绝不失败。
我们将在适当的时候学习这些保留字及其用法,但现在我们将专注于 Python 中相当于(人对狗语言中的)“说话”的指令。告诉 Python 说话的好处在于,我们甚至可以通过给它引号中的消息来告诉它要说什么:
print('Hello world!')
我们甚至已经写出了第一个语法正确的 Python 语句。我们的语句以函数 print 开头,后面跟着一个由你选择的、用单引号括起来的文本字符串。print 语句中的字符串是用引号括起来的。单引号和双引号作用相同;大多数人使用单引号,除非字符串中出现了单引号(也是撇号)。
与 Python 对话
既然我们知道了 Python 中的一个词和一个简单的语句,我们就需要知道如何开始与 Python 对话来测试我们新的语言技能。
在你与 Python 对话之前,你必须先在你的计算机上安装 Python 软件,并学会如何启动 Python。这对于本章来说细节太多了,所以我建议你查阅 www.py4e.com,那里有在 Macintosh 和 Windows 系统上设置和启动 Python 的详细说明和屏幕录像。在某个时刻,你会在终端或命令窗口中输入 python,然后 Python 解释器将以交互模式开始执行,并大致显示如下:
Python 3.11.6 (main, Nov 2 2023, 04:39:43)
[Clang 14.0.3 (clang-1403.0.22.14.1)] on darwin
Type "help", "copyright", "credits" or "license" for more
information.
>>>
>>>
提示符是 Python 解释器问你的方式:“接下来你想让我做什么?” Python 已经准备好与你对话。你只需要知道如何说 Python 语言。
比方说,你甚至不知道最简单的 Python 词汇或语句。你可能会想用宇航员登陆遥远星球并试图与该星球居民交谈时的标准台词:
>>> I come in peace, please take me to your leader
File "<stdin>", line 1
I come in peace take me tou your leader
^^^^
SyntaxError: invalid syntax
>>>
这进展不太顺利。除非你快点想出办法,否则这个星球的居民很可能会用长矛刺穿你,把你放在烤架上,用火烤熟,然后当晚餐吃掉。
幸运的是,你在旅途中带了这本书,你翻到这一页,然后重试:
>>> print('Hello world!')
Hello world!
这看起来好多了,所以你尝试进一步交流:
>>> print('You must be the legendary god that comes from the sky')
You must be the legendary god that comes from the sky
>>> print('We have been waiting for you for a long time')
We have been waiting for you for a long time
>>> print('Our legend says you will be very tasty with mustard')
Our legend says you will be very tasty with mustard
>>> print 'We will have a feast tonight unless you say
File "<stdin>", line 1
print 'We will have a feast tonight unless you say
^
SyntaxError: unterminated string literal (detected at line 1)
>>>
对话进行得一度很顺利,然后你在使用 Python 语言时犯了一个最小的错误,Python 又把长矛拿出来了。
此时,你也应该意识到,Python 虽然极其复杂、功能强大,并且对你用来与之交流的语法非常挑剔,但 Python 并不具备智能。你实际上只是在和自己对话,只不过使用了正确的语法。
从某种意义上说,当你使用别人编写的程序时,对话是在你和其他程序员之间进行的,Python 充当了中介。Python 是程序创建者用来表达对话应如何进行的一种方式。再过几章,你将成为那些使用 Python 与你的程序用户交流的程序员之一。
在我们结束与 Python 解释器的第一次对话之前,你可能应该知道在与 Python 星球的居民互动时,正确的说“再见”的方式:
>>> good-bye
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'good' is not defined
>>> if you don't mind, I need to leave
File "<stdin>", line 1
if you don't mind, I need to leave
^
SyntaxError: unterminated string literal (detected at line 1)
>>> quit()
你会注意到前两次错误尝试的报错是不同的。第二个错误不同,因为 if 是一个保留字,Python 看到了这个保留字,以为我们想说什么,但是句子的语法错了。
向 Python 说“再见”的正确方法是在交互式 >>>
提示符下输入 quit()。你可能要花很长时间才能猜到这个方法,所以手边有本书可能会很有帮助。
术语:解释器和编译器
Python 是一种高级语言,旨在让人们易于读写,也让计算机易于读取和处理。其他高级语言包括 Java、C++、PHP、Ruby、Basic、Perl、JavaScript 等等。中央处理器(CPU)内部的实际硬件并不理解任何这些高级语言。
CPU 理解一种我们称为机器语言的语言。机器语言非常简单,但坦白说编写起来非常繁琐,因为它完全由 0 和 1 表示:
001010001110100100101010000001111
11100110000011101010010101101101
...
机器语言表面上看起来很简单,因为它只有 0 和 1,但其语法甚至比 Python 更复杂、更精细。因此,很少有程序员会直接编写机器语言。相反,我们构建了各种翻译器,允许程序员用像 Python 或 JavaScript 这样的高级语言编写程序,这些翻译器将程序转换为机器语言,供 CPU 实际执行。
由于机器语言是与计算机硬件绑定的,因此机器语言在不同类型的硬件之间是不可移植的。用高级语言编写的程序可以通过在新机器上使用不同的解释器,或重新编译代码来为新机器创建程序的机器语言版本,从而在不同的计算机之间移动。
这些编程语言翻译器大致分为两类:(1) 解释器和 (2) 编译器。
解释器读取程序员编写的程序源代码,解析源代码,并即时解释指令。Python 就是一个解释器,当我们以交互方式运行 Python 时,我们可以输入一行 Python 代码(一个句子),Python 会立即处理它,并准备好让我们输入下一行 Python 代码。
有些 Python 代码行告诉 Python 你希望它记住某个值以备后用。我们需要为那个要记住的值选择一个名称,并且我们可以使用那个符号名称稍后检索该值。我们使用术语变量来指代我们用来指代这个存储数据的标签。
>>> x = 6
>>> print(x)
6
>>> y = x * 7
>>> print(y)
42
>>>
在这个例子中,我们要求 Python 记住值 6,并使用标签 x 以便我们以后可以检索该值。我们使用 print 来验证 Python 确实记住了这个值。然后我们要求 Python 检索 x 并将其乘以 7,并将新计算出的值放入 y 中。然后我们要求 Python 打印出当前 y 中的值。
尽管我们是一次一行地向 Python 输入这些命令,但 Python 将它们视为一个有序的语句序列,后面的语句能够检索在前面语句中创建的数据。我们正在以逻辑清晰、有意义的顺序编写我们的第一个包含四个句子的简单段落。
能够进行如上所示的交互式对话是解释器的特性。编译器需要接收整个程序文件,然后运行一个过程将高级源代码翻译成机器语言,然后编译器将生成的机器语言放入一个文件中以供后续执行。
如果你使用的是 Windows 系统,这些可执行的机器语言程序通常带有后缀名“.exe”或“.dll”,分别代表“可执行文件”和“动态链接库”。在 Linux 和 Macintosh 中,没有唯一标记文件为可执行文件的后缀名。
如果你用文本编辑器打开一个可执行文件,它看起来会完全是乱码,无法阅读:
^?ELF^A^A^A^@^@^@^@^@^@^@^@^@^B^@^C^@^A^@^@^@\xa0\x82
^D^H4^@^@^@\x90^]^@^@^@^@^@^@4^@ ^@^G^@(^@$^@!^@^F^@
^@^@4^@^@^@4\x80^D^H4\x80^D^H\xe0^@^@^@\xe0^@^@^@^E
^@^@^@^D^@^@^@^C^@^@^@^T^A^@^@^T\x81^D^H^T\x81^D^H^S
^@^@^@^S^@^@^@^D^@^@^@^A^@^@^@^A\^D^HQVhT\x83^D^H\xe8
....
阅读或编写机器语言并不容易,所以很高兴我们有解释器和编译器,让我们能够用像 Python 或 C 这样的高级语言编写程序。
现在,在我们讨论编译器和解释器的这个节点,你可能会对 Python 解释器本身有点好奇。它是用什么语言编写的?它是用编译型语言编写的吗?当我们输入 ‘python’ 时,究竟发生了什么?
Python 解释器是用一种名为“C”的高级语言编写的。你可以通过访问 www.python.org 并找到其源代码来查看 Python 解释器的实际源代码。所以 Python 本身就是一个程序,它被编译成机器码。当你(或供应商)在你的计算机上安装 Python 时,你将翻译后的 Python 程序的机器码副本复制到了你的系统上。在 Windows 中,Python 本身的可执行机器码很可能位于一个类似这样名称的文件中:
C:\Python35\python.exe
这比你成为一名 Python 程序员真正需要了解的要多,但有时在一开始就回答那些有点烦人的小问题是值得的。
编写程序
在 Python 解释器中输入命令是体验 Python 特性的好方法,但不建议用这种方式来解决更复杂的问题。
当我们想要编写程序时,我们使用文本编辑器将 Python 指令写入一个文件,这个文件被称为脚本。按照惯例,Python 脚本的名称以 .py
结尾。
要执行脚本,你必须告诉 Python 解释器文件的名称。在命令窗口中,你会像下面这样输入 python hello.py
:
$ cat hello.py
print('Hello world!')
$ python hello.py
Hello world!
“$” 是操作系统提示符,“cat hello.py” 向我们展示了文件 “hello.py” 中有一个打印字符串的单行 Python 程序。
我们调用 Python 解释器,并告诉它从文件 “hello.py” 中读取源代码,而不是以交互方式提示我们输入 Python 代码行。
你会注意到没有必要在文件中的 Python 程序末尾加上 quit()。当 Python 从文件中读取源代码时,它知道在到达文件末尾时停止。
什么是程序?
程序最基本的定义是:一系列为完成某项任务而精心编写的 Python 语句。即使是我们简单的 hello.py 脚本也是一个程序。它是一个单行程序,并不是特别有用,但根据最严格的定义,它是一个 Python 程序。
理解程序是什么最简单的方法可能是思考一个程序可以解决的问题,然后看看能够解决该问题的程序。
假设你在做关于 Facebook 帖子的社交计算研究,并且对一系列帖子中最常使用的词感兴趣。你可以打印出 Facebook 的帖子流,仔细阅读文本寻找最常见的词,但这将花费很长时间并且非常容易出错。明智的做法是编写一个 Python 程序来快速准确地处理这项任务,这样你就可以把周末花在做一些有趣的事情上。
例如,看看下面这段关于一个小丑和一辆汽车的文字。阅读文本并找出最常见的词以及它出现了多少次。
the clown ran after the car and the car ran into the tent
and the tent fell down on the clown and the car
然后想象一下你正在处理数百万行的文本来完成这项任务。坦白说,学习 Python 并编写一个 Python 程序来统计单词,会比你手动扫描单词更快。
更好的消息是,我已经编写了一个简单的程序来查找文本文件中最常见的单词。我编写、测试了它,现在我把它给你使用,这样你就可以节省一些时间。
name = input('Enter file: ')
handle = open(name, 'r')
counts = dict()
for line in handle:
words = line.split()
for word in words:
counts[word] = counts.get(word, 0) + 1
bigcount = None
bigword = None
for word, count in list(counts.items()):
if bigcount is None or count > bigcount:
bigword = word
bigcount = count
print(bigword, bigcount)
# 代码: https://www.py4e.com/code3/words.py
你甚至不需要懂 Python 就能使用这个程序。你需要学完本书的第 10 章才能完全理解用于制作这个程序的精妙的 Python 技术。你是最终用户,你只需使用这个程序,惊叹于它的巧妙以及它为你节省了多少手动工作。你只需将代码输入到一个名为 words.py 的文件中并运行它,或者从 http://www.py4e.com/code3/ 下载源代码并运行它。
这是 Python 和 Python 语言如何在你(最终用户)和我(程序员)之间充当中介的一个很好的例子。Python 是我们用来交换有用的指令序列(即程序)的一种方式,这种通用语言可以被任何在计算机上安装了 Python 的人使用。所以我们都不是在与 Python 对话,而是通过 Python 相互交流。
程序的构建模块
在接下来的几章中,我们将学习更多关于 Python 的词汇、句子结构、段落结构和故事结构。我们将了解 Python 的强大功能以及如何将这些功能组合起来创建有用的程序。
有一些我们用来构建程序的底层概念模式。这些构件不仅适用于 Python 程序,它们是所有编程语言的一部分,从机器语言到高级语言。
输入 (input) 从“外部世界”获取数据。这可能是从文件中读取数据,甚至是从某种传感器(如麦克风或 GPS)读取数据。在我们最初的程序中,我们的输入将来自用户在键盘上输入数据。 输出 (output) 在屏幕上显示程序的结果或将它们存储在文件中,或者将它们写入像扬声器这样的设备来播放音乐或朗读文本。 顺序执行 (sequential execution) 按照脚本中遇到的顺序,一条接一条地执行语句。 条件执行 (conditional execution) 检查特定条件,然后执行或跳过一系列语句。 重复执行 (repeated execution) 重复执行某组语句,通常带有一些变化。 重用 (reuse) 编写一组指令一次,给它们命名,然后在整个程序中根据需要重用这些指令。
这听起来几乎简单得令人难以置信,当然,事情从来没有那么简单。这就像说走路仅仅是“一只脚迈到另一只脚前面”。编写程序的“艺术”在于将这些基本元素组合、编织许多次,以产生对其用户有用的东西。
上面那个词频统计程序直接使用了除了一种之外的所有这些模式。
可能出什么错?
正如我们在与 Python 的最早对话中看到的,在编写 Python 代码时必须非常精确地进行交流。最小的偏差或错误都会导致 Python 放弃查看你的程序。
初学程序员常常将 Python 不容许任何错误这一事实视为 Python 刻薄、可恨、残忍的证据。似乎 Python 喜欢其他人,但唯独认识他们本人并对他们怀恨在心。由于这种怨恨,Python 会将我们完美编写的程序当作“不合格”而拒绝,仅仅是为了折磨我们。
>>> primt 'Hello world!'
File "<stdin>", line 1
primt 'Hello world!'
^^^^^^^^^^^^^^
SyntaxError: invalid syntax
>>> primt ('Hello world')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'primt' is not defined. Did you mean: 'print'?
>>> I hate you Python!
File "<stdin>", line 1
I hate you Python!
^^^^
SyntaxError: invalid syntax
>>> if you come out of there, I would teach you a lesson
File "<stdin>", line 1
if you come out of there, I would teach you a lesson
^^^^
SyntaxError: invalid syntax
>>>
与 Python 争论没什么好处。它只是一个工具。它没有情绪,无论何时你需要它,它都乐意并准备好为你服务。它的错误消息听起来很刺耳,但那只是 Python 在寻求帮助。它已经看了你输入的内容,但它实在无法理解你的输入。
Python 更像是一只狗,无条件地爱你,能理解几个关键词,用它可爱的表情 ( >>>
) 看着你,等待你说一些它能理解的话。当 Python 说 “SyntaxError: invalid syntax” 时,它只是在摇着尾巴说:“你好像说了些什么,但我就是不明白你的意思,但请继续跟我说话 ( >>>
)。”
随着你的程序变得越来越复杂,你会遇到三种常见的错误类型:
语法错误 (Syntax errors) 这是你会犯的第一种错误,也是最容易修复的。语法错误意味着你违反了 Python 的“语法”规则。Python 会尽力指向它注意到混淆的那一行和那个字符。语法错误的唯一棘手之处在于,有时需要修复的错误实际上可能在程序中比 Python 注意到混淆的地方更早。所以 Python 在语法错误中指示的行和字符可能只是你调查的起点。 逻辑错误 (Logic errors) 逻辑错误是指你的程序语法正确,但是语句的顺序有误,或者语句之间的关系有误。一个逻辑错误的好例子可能是:“从水瓶里喝水,把它放进背包,走到图书馆,然后盖上瓶盖。” 语义错误 (Semantic errors) 语义错误是指你描述的步骤在语法上完美无缺,顺序也正确,但程序本身就是有错误。程序完全正确,但它没有做你打算让它做的事情。一个简单的例子是,如果你给别人指路去餐馆时说:“……当你到达有加油站的路口时,左转走一英里,餐馆是你左边的一栋红色建筑。”你的朋友迟到了很久,打电话告诉你他们在一个农场里,正在谷仓后面转悠,完全没有餐馆的迹象。然后你说:“你在加油站是左转还是右转了?”他们说:“我完全按照你的指示做的,上面写着在加油站左转走一英里。”然后你说:“非常抱歉,虽然我的指示在语法上是正确的,但遗憾的是,它们包含了一个虽小却未被发现的语义错误。”
再次强调,对于所有这三种类型的错误,Python 都只是在尽力完全按照你的要求去做。
调试
当 Python 抛出错误,或者甚至当你得到的结果与你预期的不同时,寻找错误原因的征程就开始了。调试是在你的代码中查找错误原因的过程。当你调试一个程序,特别是处理一个困难的 bug 时,有四件事可以尝试:
阅读 (reading) 检查你的代码,自己回读一遍,确认它表达了你的本意。 运行 (running) 通过进行修改和运行不同版本来做实验。通常,如果在程序的正确位置显示正确的信息,问题会变得显而易见,但有时你必须花一些时间来构建脚手架。 反思 (ruminating) 花点时间思考!这是什么类型的错误:语法错误、运行时错误、语义错误?你能从错误消息或程序输出中获得什么信息?什么样的错误可能导致你看到的问题?你最后改了什么,在问题出现之前? 回退 (retreating) 在某个时刻,最好的办法是退后一步,撤销最近的更改,直到回到一个可以工作并且你理解的程序状态。然后你可以开始重新构建。
初学程序员有时会卡在其中一项活动上而忘记了其他的。找到一个困难的 bug 需要阅读、运行、反思,有时还需要回退。如果你卡在其中一项活动上,试试其他的。每项活动都有其自身的失败模式。
例如,如果问题是打字错误,阅读代码可能会有帮助,但如果问题是概念上的误解,那就没用了。如果你不理解你的程序做什么,你可以读 100 遍也发现不了错误,因为错误在你的脑子里。
运行实验可以有帮助,特别是如果你运行的是小型、简单的测试。但如果你不加思考或不阅读代码就进行实验,你可能会陷入我称之为“随机游走式编程”的模式,即随机进行修改直到程序碰巧能正确运行的过程。不用说,随机游走式编程可能需要很长时间。
你必须花时间思考。调试就像一门实验科学。你应该至少有一个关于问题所在的假设。如果有两种或更多种可能性,试着想一个可以排除其中一种的测试。
休息一下有助于思考。交谈也有帮助。如果你向别人(甚至向自己)解释问题,你有时会在问完问题之前就找到答案。
但是,如果错误太多,或者你试图修复的代码太大太复杂,即使是最好的调试技术也会失败。有时最好的选择是回退,简化程序,直到得到一个能工作且你理解的东西。
初学程序员通常不愿意回退,因为他们舍不得删除一行代码(即使它是错的)。如果这能让你感觉好受些,可以在开始精简之前将你的程序复制到另一个文件中。然后你可以一点一点地把代码片段粘贴回来。
学习之旅
在你学习本书剩余部分的过程中,如果概念在第一次接触时似乎不能很好地组合在一起,请不要害怕。当你学习说话的时候,你最初几年只会发出可爱的咿呀声,这并不是问题。从简单的词汇到简单的句子花了六个月,又花了 5-6 年才过渡到段落,再过几年才能独立写出一个有趣的完整短篇故事,这些都没关系。
我们希望你更快地学习 Python,所以在接下来的几章里,我们会同时教授所有内容。但这就像学习一门新语言,需要时间来吸收和理解,然后才能感觉自然。这会导致一些困惑,因为我们会在定义构成宏大蓝图的微小片段的同时,反复探讨各个主题,试图让你看到全局。虽然这本书是线性编写的,如果你正在上课,它也会以线性方式进行,但请不要犹豫,以一种非常非线性的方式来接触这些材料。向前看、向后看,轻松地阅读。通过略读更高级的材料而不完全理解细节,你可以更好地理解编程的“为什么?”。通过复习以前的材料,甚至重做早期的练习,你会意识到你实际上学到了很多东西,即使你目前盯着的材料看起来有点难以理解。
通常,当你学习第一门编程语言时,会有一些美妙的“啊哈!”时刻,那时你可以从用锤子和凿子敲打某块岩石的状态中抬起头来,退后一步,看到你确实在建造一个美丽的雕塑。
如果某件事看起来特别困难,通常通宵盯着它看是没有价值的。休息一下,小睡片刻,吃点零食,向某人(或者也许是你的狗)解释你遇到的问题,然后用全新的眼光再回来看它。我向你保证,一旦你学会了书中的编程概念,你会回头看到一切其实都很简单和优雅,只是需要你花一点时间来吸收它。
术语表
Bug (bug) 程序中的错误。 中央处理器 (central processing unit) 任何计算机的核心。它运行我们编写的软件;也称为“CPU”或“处理器”。 编译 (compile) 为了稍后执行,一次性将用高级语言编写的程序翻译成低级语言。 高级语言 (high-level language) 像 Python 这样设计成易于人类读写的编程语言。 交互模式 (interactive mode) 通过在提示符下输入命令和表达式来使用 Python 解释器的一种方式。 解释 (interpret) 通过逐行翻译来执行高级语言程序。 低级语言 (low-level language) 设计成便于计算机执行的编程语言;也称为“机器码”或“汇编语言”。 机器码 (machine code) 软件的最低级语言,是由中央处理器(CPU)直接执行的语言。 主内存 (main memory) 存储程序和数据。主内存在断电时会丢失信息。 解析 (parse) 检查程序并分析其语法结构。 可移植性 (portability) 程序可以在多种类型的计算机上运行的特性。 print 函数 (print function) 使 Python 解释器在屏幕上显示一个值的指令。 问题解决 (problem solving) 阐述问题、寻找解决方案并表达解决方案的过程。 程序 (program) 指定一项计算的一组指令。 提示符 (prompt) 当程序显示一条消息并暂停,等待用户向程序输入一些内容时。 辅助内存 (secondary memory) 存储程序和数据,并且即使在断电时也能保留其信息。通常比主内存慢。辅助内存的例子包括磁盘驱动器和 U 盘中的闪存。 语义 (semantics) 程序的含义。 语义错误 (semantic error) 程序中的一种错误,导致它执行了程序员意图之外的操作。 源代码 (source code) 用高级语言编写的程序。
练习
练习 1: 计算机中的辅助内存有什么功能?
a) 执行程序的所有计算和逻辑 b) 通过互联网检索网页 c) 长期存储信息,即使断电也能保存 d) 从用户那里获取输入
练习 2: 什么是程序?
练习 3: 编译器和解释器有什么区别?
练习 4: 以下哪项包含“机器码”?
a) Python 解释器 b) 键盘 c) Python 源文件 d) 文字处理文档
练习 5: 以下代码有什么问题:
>>> primt 'Hello world!'
File "<stdin>", line 1
primt 'Hello world!'
^
SyntaxError: invalid syntax
>>>
练习 6: 在以下 Python 行执行完毕后,像“x”这样的变量存储在计算机的哪个位置?
x = 123
a) 中央处理器 b) 主内存 c) 辅助内存 d) 输入设备 e) 输出设备
练习 7: 以下程序将打印出什么:
x = 43
x = x - 1
print(x)
a) 43 b) 42 c) x + 1 d) 错误,因为 x = x + 1 在数学上是不可能的
练习 8: 用人类能力的例子解释以下各项:(1) 中央处理器,(2) 主内存,(3) 辅助内存,(4) 输入设备,和 (5) 输出设备。例如,“人类中相当于中央处理器的是什么?”
如果你在本书中发现错误,欢迎使用 Github 给我发送修正。