Python-自动化指南-繁琐工作自动化-第三版-一-
Python 自动化指南(繁琐工作自动化)第三版(一)
原文:
automatetheboringstuff.com/译者:飞龙
引言

“你刚刚用两个小时完成了我们三个人需要两天才能完成的工作。”我的大学室友在 2000 年代初在一家零售电子产品店工作。偶尔,商店会收到来自其他商店的数千个产品价格的电子表格。三名员工会将电子表格打印成厚厚的纸张堆,然后分给他们。对于每个产品价格,他们会查找他们商店的价格,并记录所有竞争对手以更低价格销售的产品。这通常需要几天时间。
“你知道,如果你有打印输出的原始文件,我可以写一个程序来做这件事,”当他看到他们坐在地上,纸张散落和堆叠在周围时,他的室友告诉他们。
几个小时后,他有一个简短的程序,可以从文件中读取竞争对手的价格,在商店的数据库中找到该产品,并注明竞争对手是否更便宜。他刚开始学习编程,所以大部分时间都在编程书籍中查找文档。实际程序只用了几秒钟就运行完毕。那天,我的室友和他的同事们吃了一个很长的午餐。
这就是计算机编程的力量。电脑就像瑞士军刀,拥有无数任务的工具。许多人花费数小时点击和输入来执行重复性任务,却不知道如果他们给出正确的指令,这台机器可以在几秒钟内完成他们的工作。
这本书是为谁写的?
软件是我们今天使用的许多工具的核心:几乎每个人都使用社交网络进行交流,几乎所有的人都把互联网连接的手机放在钱包或口袋里,而且大多数办公室工作都涉及与电脑互动来完成工作。因此,对能够编写代码的人的需求激增。无数本书籍、在线教程和开发者训练营承诺将雄心勃勃的初学者培养成拥有六位数薪酬的软件工程师。
这本书不是为那些人写的。它是为其他人写的。
仅凭这本书,并不能让你成为专业的软件开发者,就像几节吉他课不能让你成为摇滚明星一样。但如果你是一名办公室工作人员、管理员、学者或任何其他使用电脑工作或娱乐的人,你将学习编程的基础知识,这样你就可以自动化这些简单的任务:
-
移动和重命名数千个文件并将它们分类到文件夹中
-
填写在线表格——无需输入
-
每次网站更新时下载文件或复制文本
-
让你的电脑文本为你的手机发送定制通知
-
更新或格式化 Excel 电子表格
-
检查你的电子邮件并发送预先编写的回复
-
创建数据库并查询信息
-
从图像和音频文件中提取文本
这些任务对人类来说简单但耗时,而且它们通常非常微不足道或特定,以至于没有现成的软件可以执行它们。然而,如果你具备一点编程知识,你就可以让计算机为你完成这些任务。
本书使用的编码约定
这本书不是设计成参考手册;它是一本面向初学者的指南。编码风格有时会违反最佳实践(例如,一些程序使用全局变量),但这种权衡使得代码更容易学习。由于增加了复杂性,以下高级编程概念——如 面向对象编程,列表推导,和 生成器——没有涵盖。经验丰富的程序员可能会指出如何修改这本书中的代码以提高效率,但本书主要关注的是以最少的努力使程序运行。
什么是编程?
电视节目和电影经常展示程序员在发光的屏幕上疯狂地输入神秘的 1s 和 0s 流,但现代编程并不那么神秘。编程 是为计算机编写计算机可以理解的指令。这些指令可能处理一些数字,修改文本,在文件中查找信息,或者通过互联网与其他计算机通信。
所有程序都使用基本指令作为构建块。以下是一些最常见的指令,用英语表示:
“这样做;然后做那件事。”
“如果这个条件成立,执行这个动作;否则,执行那个动作。”
“精确执行这个动作 27 次。”
“继续这样做,直到这个条件成立。”
你可以将这些构建块组合起来实现更复杂的决策。例如,以下是一个简单程序的编程指令,称为 源代码,它使用 Python 编程语言编写。从顶部开始,Python 软件运行代码行(其中一些仅在 如果 某个条件为真时运行,或者 否则 Python 运行其他行)直到到达底部:
password_file = open('SecretPasswordFile.txt') # ❶
secret_password = password_file.read() # ❷
print('Enter your password.') # ❸
typed_password = input()
if typed_password == secret_password: print('Access granted') ❻ if secret_password == '12345': ❼ print('That password is one that an idiot puts on their luggage.') # ❹ # ❺
else: print('Access granted') ❻ if secret_password == '12345': ❼ print('That password is one that an idiot puts on their luggage.') # ❹ # ❺
你可能对编程一无所知,但仅通过阅读,你或许可以合理猜测出前一段代码的功能。首先,文件 SecretPasswordFile.txt 被打开❶,然后读取其中的秘密密码❷。接着,程序提示用户输入密码(从键盘输入)❸。这两个密码会被比较❹,如果它们相同,程序会在屏幕上打印 Access granted❺。接下来,程序检查密码是否为 12345❻,并暗示这种选择可能不是最佳密码选择❼。如果密码不相同,程序会在屏幕上打印 Access denied❽。
编程是一项创造性的任务,就像绘画、写作、编织和建造乐高城堡一样。就像绘画的空白画布一样,软件有许多约束但无限的可能性。编程与其他创造性活动之间的区别在于,您的计算机已经包含了您编程所需的所有原材料;您不需要购买任何额外的画布、颜料、胶卷、毛线、乐高积木或电子元件。十年前的笔记本电脑足以编写程序。一旦您编写了程序,您就可以完美地复制无数次。一件针织毛衣一次只能由一个人穿着,但一个有用的程序可以轻松地在线与全世界分享。
什么是 Python?
Python 指的是一种编程语言(具有编写有效 Python 代码的语法规则)以及读取源代码(用 Python 语言编写)并执行其指令的解释器软件。您可以在 python.org 免费下载 Python 解释器的 Windows、macOS 和 Linux 版本。
有几种编程语言,每种语言都有其优点和缺点。争论哪种最好往往会导致毫无意义的争论。但在我看来,如果您是编程新手,Python 是学习的第一种最佳语言。Python 有一个平缓的学习曲线和可读的语法。它不需要学习密集的概念来完成简单的任务。如果您想进一步学习编程,在首先理解 Python 的情况下学习第二种语言会更容易。
Python 的名字来源于超现实英国喜剧团体 Monty Python,而不是蛇。Python 程序员亲切地被称为 Pythonistas,Monty Python 和蛇形参考通常遍布 Python 教程和文档中。
关于编程的常见神话
编程有着令人畏惧的名声,数十亿美元的软件公司是家喻户晓的名字。甚至英语单词 code 也与秘密和神秘的含义相关联。这使得许多人认为只有少数人能够编程。但编码是一项任何人都可以学习的技能,我想直接解决一些更常见的神话。
程序员不需要掌握太多数学知识
我听到的关于学习编程的最常见焦虑是认为它需要大量的数学知识。实际上,大多数编程不需要超出基本算术的数学知识。编程需要推理和注意细节,而不是数学。事实上,擅长编程与擅长解决数独谜题并没有太大的区别。
要解决数独谜题,你必须为每一行、每一列和整个 9×9 板中的每个 3×3 内部方格填充数字 1 到 9。谜题为你提供了一些起始数字,你可以通过基于这些数字进行推理来找到解决方案。在图 1 所示的谜题中,5 出现在第一行和第二行,所以它不能再次出现在这些行中。因此,在上右边的网格中,它必须出现在第三行。因为最后一列也已经有一个 5,所以 5 不能在 6 的右边,它必须放在 6 的左边。解决一行、一列或一个方格将为解决剩余的谜题提供更多线索,随着你填充一组数字 1 到 9,然后是另一组,你很快就会解决整个网格。

图 1:一个新的数独谜题(左)及其解决方案(右)。尽管使用数字,但数独并不涉及很多数学。(图片版权©维基媒体)
数独游戏涉及数字并不意味着你必须擅长数学才能找出解决方案。编程也是如此。就像解决数独谜题一样,编写程序需要关注细节并将问题分解成单个步骤。同样,当调试程序(即查找和修复错误)时,你会耐心地观察程序正在做什么,并找出错误的原因。就像所有技能一样,你编写的程序越多,你就会变得越好。
你并不太老而无法学习编程
我听到的关于学习编程的第二大焦虑是人们认为他们太老了学不会。我阅读了许多来自认为对他们来说已经太晚的人的互联网评论,因为他们已经(惊讶!)23 岁了。这显然并不是“太老”而无法学习编程;许多人都是在生活中更晚的时候才开始学习。
你不需要从小就开始学习才能成为一个有能力的程序员。但程序员作为天才儿童的印象是根深蒂固的。不幸的是,当我告诉别人我是在小学时开始编程的时候,我也为这个神话做出了贡献。
然而,与 20 世纪 90 年代相比,今天学习编程要容易得多。今天,有更多的书籍、更好的搜索引擎和许多在线问答网站。除此之外,编程语言本身也更加用户友好。因此,我在小学到高中毕业期间所学的所有编程知识,今天在十二个周末左右就可以学会。我的先发优势其实并不大。
对编程持有“成长心态”很重要——换句话说,理解人们通过实践来发展编程技能。他们不是生来就是程序员,现在编程技能不足并不意味着你永远无法成为专家。
人工智能不会取代程序员
在 1990 年代,纳米技术承诺改变所有的一切。新的制造工艺和科学创新将改变社会,这种观点认为。碳纳米管、富勒烯和铅笔芯的价格,所有这些都是由微小的纳米机器人逐个原子组装出来的,将铺平道路,制造出比钢强 10 倍、重量和成本却少得多的材料。这将导致太空电梯、医疗奇迹和能够制造任何东西的家用电器,就像《星际迷航》中的复制器一样!这意味着经济稀缺、世界饥饿和战争的结束。这将带来一个启蒙新时代——只要纳米机器人不会在微小的机器人起义中背叛它们的人类创造者。而且这项技术只有 10 年的时间!
当然,炒作从未发生。在纳米尺度上确实发生了真正的创新(你口袋里的智能手机就使用了其中的一些)。但《星际迷航》中的复制器和其他宏伟的承诺并没有到来,对纳米技术的兴奋也降到了更现实的水平。
让我们谈谈人工智能。
个人电脑改变了所有的一切。互联网改变了所有的一切。社交媒体改变了所有的一切。智能手机改变了所有的一切。加密货币并没有改变所有的一切,但它揭示了你的哪些表亲和同事容易受到快速致富诈骗的影响。今天,人工智能是科技行业最新出现的奇迹。人们使用“人工智能”这个术语来指代从下棋的电脑到聊天机器人、所谓的专家系统和机器学习的一切。在这本书中,我将使用“大型语言模型(LLM)”这个术语,它是 OpenAI 的 ChatGPT、Google 的 Gemini、Facebook 的 LLaMA 和其他生成文本系统背后的概念类别。
LLM 引发了许多令人喘不过气来的声明和问题。人工智能会取代我们所有的工作吗?学习编程还有意义吗?人工智能是活着的,我能在这场人工智能-机器人起义中生存下来吗?
LLM 技术令人兴奋,但为了使其有用,我们需要设定现实的期望,这样我们就可以避免成为耸人听闻的新闻和可疑的“投资机会”的受害者。我希望我能消除这种炒作,并给你一个更现实的视角,了解 LLM 如何帮助你学习编程。让我们现在就消除一些这些误解:
-
LLM 没有意识或感知能力。
-
LLM 不会取代人类软件工程师。
-
大型语言模型(LLM)并没有减轻学习编程的需求。
-
LLM 不会取代大多数人类工作(尽管这不会阻止你的经理认为他们可以,然后解雇你)。
-
LLM 远非完美,甚至经常是错误的。
坚持这些错误观念的人,他们的信息来源是科幻电影和互联网视频,而不是来自实际 LLMs 的经验。我强烈推荐 Simon Willison,Python 软件基金会董事会成员和 Django Web 框架的共同创造者,他关于 AI 的写作可以在simonwillison.net/about上找到。你可以在autbor.com/pycon2024keynote上观看他关于 LLMs 的清醒而富有洞察力的 PyCon 2024 主题演讲。
LLMs 如何帮助你作为程序员?在早期阶段,显而易见的是,学习与 LLMs 沟通(所谓的提示工程)是一项技能,就像学习有效地使用搜索引擎是一项技能一样。LLMs 不是人,你需要学习如何措辞你的问题以获得相关和可靠的答案。当 LLMs 自信地编造错误答案时,我们说它们在“幻觉”。但这只是将算法拟人化的另一种方式。LLMs 没有思考;它们生成文本。也就是说,LLMs 总是“幻觉”的,即使它们的答案碰巧是正确的。
尽管 LLMs 会犯大而明显的错误和简单而微妙的错误。如果你将它们作为学习工具使用,你必须警惕地检查 LLM 告诉你的每一件事,无论大小。完全有理由选择完全放弃使用 LLMs 进行学习。到目前为止,使用 LLMs 进行教育的有效性尚未得到证实。我们不知道在什么情况下 LLMs 作为学习工具是有用的,甚至不知道它们的益处是否超过了成本。
毫不奇怪,如此多人会相信 LLMs 的炒作。我们口袋里装着科学家上个世纪会认为是超级计算机的设备。它们将我们连接到全球的信息网络(以及错误信息)。它们可以通过监听外太空的卫星来确定地球上任何位置。软件已经能够做看似神奇的事情,但软件并不是魔法。通过学习编程,你会对计算机的能力有一个更加扎实的认识,而不是仅仅停留在炒作上。
关于本书
本书的第一部分教你如何用 Python 编程。第二部分涵盖了各种用于自动化不同任务的软件库。我建议你按照顺序阅读第一部分的所有章节,然后跳到第二部分中你感兴趣的部分。以下是每个章节的简要概述。
第一部分:编程基础
第一章:Python 基础 介绍了最基础的 Python 指令类型——表达式,以及如何使用 Python 交互式 shell 软件进行代码实验。
第二章:if-else 和流程控制 解释了如何让程序决定执行哪些指令,以便你的代码能够智能地响应不同的条件。
第三章:循环 解释了如何使程序重复执行一组指令一定次数,或者直到某个条件成立。
第四章:函数 指导你如何定义自己的函数,以便你可以将代码组织成更易于管理的块。
第五章:调试 展示如何使用 Python 的各种错误查找和修复工具。
第六章:列表 介绍了列表数据类型并解释了如何组织数据。
第七章:字典和数据结构 介绍了字典数据类型并展示了更强大的组织数据的方法。
第八章:字符串和文本编辑 介绍如何处理文本数据(在 Python 中称为字符串)。
第二部分:自动化任务
第九章:使用正则表达式进行文本模式匹配 涵盖了 Python 如何操作字符串并使用正则表达式搜索文本模式。
第十章:读取和写入文件 解释你的程序如何读取文本文件的内容并将信息保存到硬盘上的文件中。
第十一章:文件组织 展示 Python 如何比人类用户更快地复制、移动、重命名和删除大量文件。还解释了文件的压缩和解压缩。
第十二章:设计和部署命令行程序 解释了如何打包你的 Python 程序,以便你可以在自己的计算机或同事的计算机上轻松运行它们。
第十三章:网络爬虫 展示了如何编写程序自动下载网页并解析其中的信息。这被称为网络爬虫。
第十四章:Excel 电子表格 涵盖了如何使用 Python 编程方式操作 Excel 电子表格,这样你就不必阅读它们。当你需要分析的文档数量达到数百或数千时,这非常有用。
第十五章:Google 表格 涵盖了如何使用 Python 读取和更新流行的基于网络的电子表格应用 Google Sheets。
第十六章:SQLite 数据库 解释如何使用SQLite,这是一个小巧但功能强大的开源数据库,它随 Python 一起提供。
第十七章:PDF 和 Word 文档 涵盖了如何编程读取 Word 和 PDF 文档。
第十八章:CSV、JSON 和 XML 文件 继续解释如何编程操作文档,现在讨论数据序列化格式 CSV、JSON 和 XML。
第十九章:计时、任务调度和程序启动 介绍 Python 程序如何处理 时间和日期,以及如何安排您的计算机在特定时间执行任务。还展示了您的 Python 程序如何启动非 Python 程序。
第二十章:发送电子邮件、短信和推送通知 介绍如何编写可以通过电子邮件或移动通信通知您,或将这些消息发送给其他人的程序。
第二十一章:制作图表和操作图像 介绍如何通过编程方式操作图像,例如 JPEG 或 PNG 文件,以及如何使用 Matplotlib 图表制作库。
第二十二章:识别图像中的文本 介绍如何使用 PyTesseract 包从图像和扫描文档中提取文本以进行进一步处理。
第二十三章:控制键盘和鼠标 介绍如何通过编程方式控制鼠标和键盘来自动化点击和按键操作。
第二十四章:文本到语音和语音识别引擎 介绍如何使用高级计算机科学包不仅可以从文本生成语音音频,还可以将语音音频转换为文本。
您可以在此书的示例中下载源代码和其他资源,网址为 nostarch.com/automate-boring-stuff-python-3rd-edition。要查看本书许多程序的实际运行情况,请访问 autbor.com/3。
下载和安装 Python
您可以免费下载适用于 Windows、macOS 和 Ubuntu Linux 的 Python,网址为 python.org/downloads。
下载页面会检测您的操作系统并推荐适合您计算机的下载包。Android 和 iOS 移动操作系统的非官方 Python 安装程序超出了本书的范围。Windows、macOS 和 Linux 也有自己的安装选项。下载适用于您操作系统的安装程序并运行程序以安装 Python 解释器软件。
Python 或您操作系统的最新版本可能会改变安装 Python 所需的步骤。如果您遇到困难,可以查阅 autbor.com/install/ 以获取最新的说明。
下载和安装 Mu
虽然Python 解释器是运行您的 Python 程序的软件,但Mu 编辑器软件是您输入程序的地方,就像在文字处理程序中输入文本一样。您可以从 codewith.mu 下载 Mu。
在 Windows 和 macOS 上,下载适用于您操作系统的安装程序,然后通过双击安装文件来运行它。如果您在 macOS 上,运行安装程序会打开一个窗口,您必须将 Mu 图标拖到应用程序文件夹图标上以继续安装。如果您在 Ubuntu 上,您需要将 Mu 作为 Python 包安装。在这种情况下,点击下载页面 Python 包部分中的 说明 按钮。
开始使用 Mu
安装完成后,您就可以开始使用 Mu 了:
-
在 Windows 上,点击屏幕左下角的开始图标,在搜索框中输入 Mu 编辑器,然后选择它。
-
在 macOS 上,打开 Finder 窗口,点击 应用程序,然后点击 mu-editor。您也可以通过 Spotlight 运行 Mu 编辑器。
-
在 Ubuntu 上,选择 应用程序附件终端,然后输入 python3 –m mu。
第一次运行 Mu 时,会出现一个选择模式窗口,其中包含 Adafruit CircuitPython、BBC micro:bit、Pygame Zero、Python 3 等选项。选择 Python 3。您可以在以后通过点击编辑器窗口顶部的 模式 按钮更改模式。
开始使用 IDLE
本书使用 Mu 作为编辑器和交互式外壳。然而,您可以使用任意数量的编辑器来编写 Python 代码。与 Python 一起安装的 交互式开发环境 (IDLE) 软件可以作为第二个编辑器使用,如果您由于某种原因无法安装或使用 Mu。现在让我们开始 IDLE(假设您安装的版本是 Python 3.13):
-
在 Windows 上,点击屏幕左下角的开始图标,在搜索框中输入 IDLE,然后选择 IDLE (Python 3.13 64-bit)。
-
在 macOS 上,打开 Finder 窗口,点击 应用程序,点击 Python 3.13,然后点击 IDLE 图标。您也可以通过 Spotlight 运行 IDLE。
-
在 Ubuntu 上,选择 应用程序附件终端,然后输入 idle。(您也可能能够点击 Ubuntu 侧边栏底部的 显示应用 并然后点击 IDLE。)
交互式外壳
当您运行 Mu 时,出现的窗口被称为 文件编辑器 窗口。您可以通过点击 REPL 按钮打开 交互式外壳。一个 外壳 是一个程序,允许您向计算机输入指令,类似于 macOS 和 Windows 上的终端或命令提示符。Python 的解释器软件将立即运行您在 Python 交互式外壳中输入的任何指令。
在 Mu 中,交互式外壳是窗口下半部分的窗格,内容类似于以下文本:
Jupyter QtConsole
Python 3
Type 'copyright', 'credits' or 'license' for more information
IPython -- An enhanced Interactive Python. Type '?' for help.
`In [1]:`
如果您运行 IDLE,交互式外壳是首先出现的窗口。它应该大部分是空的,除了看起来像这样的文本:
>>>
In [1]: 和 >>> 被称为提示符。本书中的示例将使用 >>> 提示符来表示交互式外壳,因为它更为常见。如果你从终端或命令提示符运行 Python,它们也会使用 >>> 提示符。In [1]: 提示符是由 Jupyter Notebook 发明的,另一个流行的 Python 编辑器。
例如,在提示符旁边输入以下内容到交互式外壳中:
>>> print('Hello, world!')
在你写下这一行并按下回车键后,交互式外壳应该会显示以下内容:
>>> print('Hello, world!')
`Hello, world!`
你刚刚给计算机下达了一个指令,它按照你的指示做了!
如何寻找帮助
程序员倾向于通过在互联网上搜索问题的答案来学习。这与许多人习惯的学习方式——通过面对面的老师讲授和解答问题——截然不同。使用互联网作为教室的好处在于,有整个社区的人可以帮助你解决问题。确实,你的问题可能已经被回答,而且答案已经在网上等待你去发现。如果你遇到错误消息或代码运行出现困难,你不会是第一个遇到这个问题的人,找到解决方案比你想象的要容易。
例如,让我们故意引发一个错误:在交互式外壳中输入 '42' + 3。你现在不需要知道这个指令的含义,但结果应该看起来像这样:
>>> '42' + 3
Traceback (most recent call last): # ❶
File "<pyshell#0>", line 1, in <module>
'42' + 3
TypeError: can only concatenate str (not "int") to str # ❷
>>>
错误消息❷出现是因为 Python 无法理解你的指令。错误消息的回溯部分❶显示了 Python 遇到困难的具体指令和行号。如果你不确定如何处理特定的错误消息,可以在网上搜索。在你的搜索引擎中输入 “TypeError: can only concatenate str (not "int") to str”(包括引号),你应该会看到大量链接解释错误消息的含义和原因,如图 2 所示。

图 2:错误消息的 Google 搜索结果可能非常有帮助。
你经常会发现别人也有和你一样的问题,而且已经有其他热心的人给出了答案。没有人能对编程的所有知识都了如指掌,所以查找技术问题的答案成为任何软件开发人员日常工作的一部分。
提出聪明的编程问题
如果你在网上搜索找不到答案,可以尝试在像 Stack Overflow(stackoverflow.com)或reddit.com/r/learnprogramming这样的“学习编程”subreddit 上提问。但请记住,有聪明的方法来提问编程问题,这有助于他人帮助你。首先,请确保阅读这些网站上的常见问题解答部分,了解如何正确地发布问题。
当提问编程问题时,请记住以下几点:
-
解释你试图做什么,而不仅仅是你已经做了什么。这可以让你的帮助者知道你是否走错了方向。
-
指定错误发生的位置。是在程序一开始就出现,还是在你执行了某些操作之后才出现?
-
将整个错误信息和你的代码复制粘贴到
pastebin.com或gist.github.com。这些网站使你能够轻松地在网上与他人分享大量代码,而不会丢失任何文本格式。然后你可以将发布代码的 URL 放在你的电子邮件或论坛帖子中。例如,以下是我发布的一些代码的位置:pastebin.com/2k3LqDsd和gist.github.com/asweigart/6912168。 -
解释你已经尝试过什么来解决你的问题。这告诉人们你已经投入了一些工作,试图自己解决问题。
-
列出你使用的 Python 版本。也要说明你正在运行的操作系统及其版本。
-
如果错误是在你修改代码后出现的,请详细说明你具体做了什么更改。
-
说明你是否每次运行程序都能重现错误,或者错误是否只在执行某些操作后出现。在后一种情况下,也要解释这些操作是什么。
总是遵循良好的在线礼仪。例如,不要用全部大写字母发问,也不要对试图帮助你的人提出不合理的要求。
你可以在以下博客文章中找到有关如何请求编程帮助的更多信息:autbor.com/help。我喜欢帮助人们发现 Python。我在我的博客inventwithpython.com/blog上写编程教程,你可以通过al@inventwithpython.com与我联系提问,尽管你通过在reddit.com/r/inventwithpython上发问可能会得到更快的回复。
新版第三版
这本全面修订和更新的版本包括了 16 个新的编程项目,以便你可以练习你将学到的技能。你将找到使用 Python 的 sqlite3 模块对 SQLite 和关系数据库的完整介绍。你将探索如何在 Windows、macOS 和 Linux 上编译 Python 脚本为可执行程序;以及创建命令行程序并运行它们。你还将学习如何使用PyPDF和 PdfMiner 执行 PDF 操作,使用 Playwright 以及 Selenium 来控制网络浏览器,让你的程序通过文本到语音库进行语音输出,使用 OpenAI 的 Whisper 库从音频和视频文件创建文本转录,使用 PyTesseract 从图像中提取文本,使用 matplotlib 创建图表,等等。
摘要
对于大多数人来说,他们的电脑只是一个电器而不是工具。但通过学习如何编程,你将获得进入现代世界最强大工具之一的方法,并且你会在学习过程中感到乐趣。编程不是脑外科手术——业余爱好者进行实验和犯错是完全可以接受的。
本书假设你没有任何编程知识,并将教会你很多,但你可能有一些超出其范围的问题。记住,提出有效的问题和知道如何找到答案是你在编程旅程中无价的工具。
让我们开始吧!
这本书是为谁写的?
软件是我们今天使用的许多工具的核心:几乎每个人都使用社交网络进行交流,几乎所有的人都把互联网连接的手机放在钱包或口袋里,而且大多数办公室工作都涉及与电脑交互来完成工作。因此,对能够编写代码的人的需求急剧上升。无数本书籍、在线教程和开发者训练营承诺将雄心勃勃的初学者培养成拥有六位数薪酬的软件工程师。
这本书不是为这些人写的。它是为其他人写的。
仅凭这本书本身,并不能让你成为专业的软件开发者,就像几节吉他课不能让你成为摇滚明星一样。但如果你是一名办公室工作人员、管理员、学者或任何其他使用电脑工作或娱乐的人,你将学习编程的基础知识,这样你就可以自动化这些简单的任务:
-
移动和重命名数千个文件并将它们整理到文件夹中
-
填写在线表格——无需输入
-
在网站更新时下载文件或复制文本
-
让你的电脑向你的手机发送自定义通知
-
更新或格式化 Excel 电子表格
-
检查你的电子邮件并发送预先编写的回复
-
创建数据库并查询它们以获取信息
-
从图像和音频文件中提取文本
这些任务对人类来说简单但耗时,而且它们通常非常微不足道或特定,以至于没有现成的软件来执行它们。然而,有了少量的编程知识,你就可以让你的电脑为你完成这些任务。
本书使用的编码规范
这本书不是设计成参考手册;它是一本针对初学者的指南。编码风格有时会违反最佳实践(例如,一些程序使用全局变量),但这种权衡使得代码更容易学习。复杂的编程概念——如面向对象编程、列表推导和生成器——由于它们增加的复杂性而没有涉及。经验丰富的程序员可能会指出如何改变这本书中的代码以提高效率,但本书主要关注以最少的努力使程序运行。
什么是编程?
电视节目和电影经常展示程序员在发光的屏幕上狂敲神秘的 1s 和 0s 流,但现代编程并不那么神秘。编程是为计算机编写指令,以便计算机能够理解的语言。这些指令可能处理一些数字,修改文本,在文件中查找信息,或者通过互联网与其他计算机通信。
所有程序都使用基本指令作为构建块。以下是一些最常见的基本指令,用英语表示:
“这样做;然后做那件事。”
“如果这个条件为真,执行这个动作;否则,执行那个动作。”
“精确执行这个动作 27 次。”
“继续这样做,直到这个条件为真。”
你可以将这些构建块组合起来实现更复杂的决策。例如,以下是一个用Python编程语言编写的简单程序的编程指令,称为源代码。从顶部开始,Python 软件运行代码行(其中一些仅在如果某个条件为真时运行,或者否则Python 运行其他行)直到到达底部:
password_file = open('SecretPasswordFile.txt') # ❶
secret_password = password_file.read() # ❷
print('Enter your password.') # ❸
typed_password = input()
if typed_password == secret_password: print('Access granted') ❻ if secret_password == '12345': ❼ print('That password is one that an idiot puts on their luggage.') # ❹ # ❺
else: print('Access granted') ❻ if secret_password == '12345': ❼ print('That password is one that an idiot puts on their luggage.') # ❹ # ❺
你可能对编程一无所知,但仅通过阅读,你也许可以合理地猜测前面的代码做了什么。首先,打开文件SecretPasswordFile.txt❶,并读取其中的秘密密码❷。然后,提示用户输入密码(从键盘输入)❸。比较这两个密码❹,如果它们相同,程序将在屏幕上打印Access granted❺。接下来,程序检查密码是否为12345❻,并暗示这个选择可能不是最佳密码选择❽。如果密码不相同,程序将在屏幕上打印Access denied❽。
编程是一项创造性的任务,就像绘画、写作、编织和建造乐高城堡一样。就像绘画的空白画布一样,软件有许多限制,但可能性无限。编程与其他创造性活动之间的区别在于,您的计算机已经包含了您编程所需的所有原材料;您不需要购买任何额外的画布、颜料、胶卷、毛线、乐高积木或电子元件。十年前的笔记本电脑足以编写程序。一旦您编写了程序,您就可以完美地复制无数次。一件针织毛衣一次只能由一个人穿着,但一个有用的程序可以轻松地在线与全世界分享。
什么是 Python?
Python 指的是一种编程语言(具有编写被认为是有效 Python 代码的语法规则)以及读取源代码(用 Python 语言编写)并执行其指令的解释器软件。您可以在 python.org 免费下载 Python 解释器的 Windows、macOS 和 Linux 版本。
有多种编程语言,每种语言都有其优势和劣势。争论哪种最好往往会导致毫无意义的争论,这些争论通常基于个人观点。但在我看来,如果您是编程新手,Python 是学习的第一种最佳语言。Python 的学习曲线平缓,语法易读。它不需要学习密集的概念来完成简单任务。如果您想进一步学习编程,在首先理解 Python 的情况下学习第二种语言会更容易。
Python 这个名字来自超现实英国喜剧团体蒙提·派森,而不是来自蛇。Python 程序员亲切地被称为 Pythonistas,蒙提·派森和蛇形参考通常遍布 Python 教程和文档。
关于编程的常见误区
编程有着令人畏惧的名声,数十亿美元的软件公司是家喻户晓的名字。甚至英语单词 code 也与秘密和神秘的含义相关联。这使得许多人认为只有少数人能够编程。但编码是一项任何人都可以学习的技能,我想直接解决一些更常见的误区。
程序员不需要掌握太多数学知识
我听到关于学习编程的最常见的焦虑是认为它需要大量的数学知识。实际上,大多数编程不需要超出基本算术的数学知识。编程需要推理和注意细节,而不是数学。事实上,擅长编程与擅长解决数独谜题并没有太大的区别。
要解决数独谜题,你必须为每一行、每一列和整个 9×9 棋盘的每个 3×3 内部方格填充数字 1 到 9。谜题为你提供了一些起始数字,你可以通过基于这些数字进行推理来找到解决方案。在图 1 所示的谜题中,5 出现在第一行和第二行,所以它不能再次出现在这些行中。因此,在上右方格中,它必须出现在第三行。因为最后一列也已经有一个 5,所以 5 不能放在 6 的右边,所以它必须放在 6 的左边。解决一行、一列或一个方格将为解决剩余的谜题提供更多线索,并且随着你填充一组数字 1 到 9,然后是另一组,你很快就会解决整个网格。

图 1:一个新的数独谜题(左)及其解决方案(右)。尽管使用数字,但数独游戏并不涉及太多数学。(图片版权©维基媒体)
数独游戏涉及数字并不意味着你必须擅长数学才能找出解决方案。编程也是如此。就像解决数独谜题一样,编写程序需要关注细节并将问题分解成单个步骤。同样,当调试程序(即查找和修复错误)时,你会耐心地观察程序正在做什么,并找出错误的原因。就像所有技能一样,你编写的程序越多,你就会变得越好。
你并不因为年纪太大而无法学习编程
我听到的关于学习编程的第二大焦虑是人们认为他们年纪太大而无法学习。我阅读了许多来自认为对他们来说已经太晚的人的互联网评论,因为他们已经(惊讶!)23 岁了。这显然并不是“年纪太大”而不能学习编程;许多人都是在生活中较晚的时候才开始学习的。
你不需要从小开始学习就能成为一名有能力的程序员。但程序员是神童的形象一直持续存在。不幸的是,当我告诉别人我是在小学时开始编程的时候,我可能也在助长这个神话。
然而,与 20 世纪 90 年代相比,今天学习编程要容易得多。现在有更多的书籍、更好的搜索引擎和许多在线问答网站。除此之外,编程语言本身也更加用户友好。因此,我在小学到高中毕业期间所学的所有编程知识,今天在十二个周末左右就可以学会。我的先发优势其实并不大。
对编程持有“成长心态”很重要——换句话说,理解人们通过实践来发展编程技能。他们不是天生就是程序员,现在编程技能不熟练并不意味着你永远无法成为专家。
人工智能不会取代程序员
在 20 世纪 90 年代,纳米技术承诺改变一切。新的制造工艺和科学创新将彻底改变社会,这种观点认为。碳纳米管、富勒烯和铅笔芯的价格,所有这些都是由微米大小的纳米机器人逐个原子组装出来的,将铺平道路,制造出比钢强 10 倍的材料,重量和成本却只有一小部分。这将导致太空电梯、医疗奇迹和能够制造任何东西的家用电器,就像《星际迷航》中的复制器一样!这意味着经济稀缺、世界饥饿和战争的结束。这将带来一个新时代的启蒙——只要纳米机器人不会在微小的机器人起义中背叛它们的人类创造者。而且这项技术离我们只有 10 年!
当然,炒作从未发生。确实在纳米尺度上发生了真正的创新(你口袋里的智能手机就使用了其中的一些)。但《星际迷航》中的复制器和其他宏伟的承诺并未到来,对纳米技术的兴奋也降到了更现实的水平。
让我们谈谈人工智能。
个人电脑改变了所有的一切。互联网改变了所有的一切。社交媒体改变了所有的一切。智能手机改变了所有的一切。加密货币并没有改变所有的一切,但它确实揭示了你的哪些表亲和同事容易受到快速致富诈骗的诱惑。今天,人工智能是科技行业最新出现的奇迹。人们使用“人工智能”这个术语来指代从下棋计算机到聊天机器人、所谓的专家系统和机器学习的一切。在这本书中,我将使用“大型语言模型(LLM)”这个术语,它是 OpenAI 的 ChatGPT、谷歌的 Gemini、Facebook 的 LLaMA 和其他生成文本系统背后的概念类别。
LLM 引发了众多令人喘不过气的声明和问题。人工智能会取代我们所有的工作吗?学习编程还值得吗?人工智能是活着的,我能在这场人工智能-机器人起义中生存下来吗?
LLM 技术令人兴奋,但为了使其有用,我们需要设定现实的期望,这样我们就可以避免成为耸人听闻的新闻和可疑的“投资机会”的受害者。我希望我能消除炒作,给你一个更现实的看法,了解 LLM 如何帮助你学习编程。让我们现在就消除一些这些误解:
-
LLM 没有意识或感知。
-
大型语言模型(LLM)不会取代人类软件工程师。
-
LLM 并不能减轻学习编程的需要。
-
LLM 不会取代大多数人类工作(尽管这不会阻止你的经理认为他们可以,然后无论如何都解雇你)。
-
LLM 远非完美,甚至经常是错误的。
坚持这些错误观念的人,他们的信息来源是科幻电影和互联网视频,而不是来自实际 LLMs(大型语言模型)的经验。我强烈推荐 Simon Willison,Python 软件基金会董事会成员和 Django Web 框架的联合创造者,他在simonwillison.net/about上的关于 AI 的文章。你可以在autbor.com/pycon2024keynote上观看他关于 LLMs 的清醒且富有启发性的 PyCon 2024 主题演讲。
LLMs 如何帮助程序员?在早期阶段,显而易见的是,学习与 LLMs 沟通(所谓的提示工程)是一项技能,就像学习有效地使用搜索引擎是一项技能一样。LLMs 不是人,你需要学习如何措辞你的问题以获得相关和可靠的答案。当 LLMs 自信地编造错误答案时,我们说它们在“产生幻觉”。但这只是将算法拟人化的另一种方式。LLMs 没有思考;它们生成文本。也就是说,LLMs 总是“产生幻觉”,即使它们的答案碰巧是正确的。
然而,LLMs 会犯大而明显的错误,也会犯简单而微妙的错误。如果你把它们作为学习工具使用,你必须警惕地检查 LLM 告诉你的每一件事,无论大小。完全可以选择完全放弃使用 LLMs 进行学习。到目前为止,使用 LLMs 在教育中的有效性尚未得到证实。我们不知道在什么情况下 LLMs 作为学习工具是有用的,甚至不知道它们的益处是否超过了成本。
没有什么好奇怪的,这么多人都会相信 LLMs 的炒作。我们口袋里装着科学家上个世纪会认为是超级计算机的设备。它们连接我们到一个全球性的信息网络(以及错误信息)。它们可以通过监听外太空的卫星来确定地球上任何位置的位置。软件已经能够做看似神奇的事情,但软件并不是魔法。通过学习编程,你会对计算机的能力有一个更加扎实的认识,以及什么是炒作。
程序员不需要掌握太多数学
我听到关于学习编程最常见的焦虑是认为它需要大量的数学知识。实际上,大多数编程不需要超出基本算术的数学知识。编程需要的是推理和注重细节,而不是数学。事实上,擅长编程与擅长解决数独谜题并没有太大的区别。
要解决数独谜题,你必须为每一行、每一列以及整个 9×9 棋盘的每个 3×3 内部方格填入数字 1 到 9。谜题为你提供一些数字作为起点,你可以通过基于这些数字进行推理来找到解决方案。在图 1 所示的谜题中,5 出现在第一行和第二行,所以它不能再次出现在这些行中。因此,在上右方格中,它必须出现在第三行。因为最后一列也已经有一个 5,所以 5 不能在 6 的右边,它必须放在 6 的左边。解决一行、一列或一个方格将为解决剩余的谜题提供更多线索,当你填入一组数字 1 到 9,然后是另一组,你很快就会解决整个网格。

图 1:一个新的数独谜题(左)及其解决方案(右)。尽管使用数字,数独并不涉及很多数学。(图片版权©维基媒体)
数独游戏涉及数字并不意味着你必须擅长数学才能找出解决方案。编程也是如此。就像解决数独谜题一样,编写程序需要关注细节并将问题分解成单个步骤。同样,当调试程序(即查找和修复错误)时,你会耐心地观察程序正在做什么,并找出错误的原因。就像所有技能一样,你编写的程序越多,你将变得越好。
你学习编程永远不会太老
我听到的关于学习编程的第二大焦虑是人们认为他们太老了学不会。我阅读了许多来自认为对他们来说已经太晚的人的互联网评论,因为他们已经(惊讶!)23 岁了。这显然不是“太老”学习编程;许多人是在生活中更晚的时候学习的。
你不需要从小开始学习就能成为一名有能力的程序员。但程序员是神童的形象一直持续存在。不幸的是,当我告诉别人我是在小学时开始编程的时候,我可能也在助长这个神话。
然而,今天学习编程比 20 世纪 90 年代要容易得多。今天,有更多的书籍、更好的搜索引擎和更多的在线问答网站。除此之外,编程语言本身也变得更加用户友好。因此,我在小学到高中毕业期间学到的所有关于编程的知识,今天可以在大约十二个周末内学会。我的先发优势其实并不大。
对编程持有“成长心态”是很重要的——换句话说,理解人们通过实践来发展编程技能。他们不是生来就是程序员,现在编程技能不熟练并不意味着你永远无法成为专家。
AI 不会取代程序员
在 1990 年代,纳米技术承诺改变一切。新的制造工艺和科学创新将改变社会,这种观点认为。碳纳米管、富勒烯和铅笔芯的价格,所有这些都是由细菌大小的纳米机器人一次组装一个原子,将铺平材料之路,这些材料的强度是钢的十倍,重量和成本却少得多。这将导致太空电梯、医疗奇迹和能够制造任何东西的家用电器,就像《星际迷航》中的复制器一样!这意味着经济稀缺、世界饥饿和战争的结束。这将带来一个新时代的启蒙——只要纳米机器人不会在微小的机器人起义中背叛它们的人类创造者。而且这项技术离我们只有 10 年的时间!
当然,炒作从未发生。在纳米尺度上确实发生了真正的创新(你口袋里的智能手机就使用了其中的一些)。但《星际迷航》中的复制器和其他宏伟的承诺并没有到来,对纳米技术的兴奋也降到了更现实的水平。
让我们谈谈 AI。
个人电脑改变了一切。互联网改变了一切。社交媒体改变了一切。智能手机改变了一切。加密货币并没有改变一切,但它揭示了你的哪些表亲和同事容易受到快速致富诈骗的诱惑。今天,AI 是从科技行业涌现出的最新奇迹。人们使用 AI 这个词来指代从下棋的电脑到聊天机器人、所谓的专家系统和机器学习的一切。在这本书中,我将使用 大型语言模型 (LLM) 这个术语,它是 OpenAI 的 ChatGPT、Google 的 Gemini、Facebook 的 LLaMA 和其他生成文本系统背后的概念类别。
LLMs 引发了许多令人喘不过气的声明和问题。AI 会取代我们所有的工作吗?学习编程还值得吗?AI 是活着的,我能在这场 AI-机器人起义中存活下来吗?
LLM 技术令人兴奋,但为了使其有用,我们需要设定现实的期望,这样我们就可以避免成为耸人听闻的新闻和可疑的“投资机会”的受害者。我希望我能消除炒作,并给你一个更现实的视角,了解 LLMs 如何帮助你学习编程。让我们现在就消除一些这些误解:
-
LLMs 没有意识或感知能力。
-
LLMs 不会取代人类软件工程师。
-
LLMs 并不能减轻学习编程的需求。
-
LLMs 不会取代大多数人类工作(尽管这不会阻止你的经理认为他们可以,然后还是解雇你)。
-
LLMs 远非完美,甚至经常是错误的。
坚持这些错误观念的人,他们的信息来源于科幻电影和互联网视频,而不是来自实际 LLMs 的经验。我强烈推荐 Simon Willison,Python 软件基金会董事会成员和 Django Web 框架的联合创造者,他关于 AI 的写作可以在simonwillison.net/about上找到。你可以在autbor.com/pycon2024keynote上观看他关于 LLMs 的清醒而富有洞察力的 PyCon 2024 主题演讲。
LLMs 如何帮助作为程序员的你?在早期阶段,显而易见的是,学习与 LLM 进行有效沟通(所谓的提示工程)是一项技能,就像学习如何有效地使用搜索引擎是一项技能一样。LLMs 不是人,你需要学习如何措辞你的问题以获得相关和可靠的答案。当 LLMs 自信地给出错误答案时,我们说它们在“产生幻觉”。但这只是将算法拟人化的另一种方式。LLMs 没有思考;它们生成文本。也就是说,LLMs 总是“产生幻觉”,即使它们的答案碰巧是正确的。
LLMs(大型语言模型)会犯大而明显的错误,也会犯简单而微妙的错误。然而,如果你把它们用作学习辅助工具,你必须警惕地检查 LLM 告诉你的每一件事,无论大小。完全可以选择完全放弃使用 LLMs 进行学习。到目前为止,使用 LLMs 在教育中的有效性尚未得到证实。我们不知道在什么情况下 LLMs 作为学习工具是有用的,甚至不知道它们的益处是否超过了它们的成本。
没有什么奇怪的,这么多人都会相信 LLMs 的炒作。我们口袋里装着科学家上个世纪会认为是超级计算机的设备。它们将我们连接到全球的信息网络(以及错误信息)。它们可以通过监听外太空的卫星来确定地球上任何位置的位置。软件已经能够做看似神奇的事情,但软件不是魔法。通过学习编程,你将获得一个更扎实的想法,了解计算机的能力与炒作之间的区别。
关于本书
本书的第一部分教你如何用 Python 编程。第二部分涵盖了自动化各种任务的软件库。我建议按顺序阅读第一部分的内容,然后跳到第二部分中你感兴趣的部分。以下是每个章节的简要概述。
第一部分:编程基础
第一章:Python 基础 介绍了最基本类型的 Python 指令表达式,以及如何使用 Python 交互式 shell 软件进行代码实验。
第二章:if-else 和流程控制 解释了如何让程序决定执行哪些指令,以便你的代码能够智能地响应不同的条件。
第三章:循环 解释了如何使程序重复执行一组指令,或者直到某个条件保持不变。
第四章:函数 指导你如何定义自己的函数,以便将你的代码组织成更易于管理的块。
第五章:调试 展示了如何使用 Python 的各种错误查找和修复工具。
第六章:列表 介绍了列表数据类型并解释了如何组织数据。
第七章:字典和数据结构 介绍了字典数据类型并展示了更强大的组织数据的方法。
第八章:字符串和文本编辑 覆盖了与文本数据(在 Python 中称为字符串)一起工作的内容。
第二部分:自动化任务
第九章:使用正则表达式进行文本模式匹配 介绍 Python 如何使用正则表达式操作字符串和搜索文本模式。
第十章:读取和写入文件 解释了你的程序如何读取文本文件的内容并将信息保存到硬盘上的文件中。
第十一章:组织文件 展示了 Python 如何以比人类用户快得多地复制、移动、重命名和删除大量文件。还解释了文件的压缩和解压缩。
第十二章:设计和部署命令行程序 解释了如何打包你的 Python 程序,以便在个人电脑或同事的电脑上轻松运行。
第十三章:网络爬虫 展示了如何编写可以自动下载网页并解析信息的程序。这被称为网络爬虫。
第十四章:Excel 电子表格 覆盖了以编程方式操作 Excel 电子表格,这样你就不必读取它们。当你需要分析的文档数量达到数百或数千时,这很有帮助。
第十五章:Google Sheets 介绍如何使用 Python 读取和更新流行的基于网络的电子表格应用 Google Sheets。
第十六章:SQLite 数据库 解释了如何使用SQLite,Python 附带的小巧但强大的开源数据库,来使用关系数据库。
第十七章:PDF 和 Word 文档 覆盖了以编程方式读取 Word 和 PDF 文档。
第十八章:CSV、JSON 和 XML 文件 继续解释如何以编程方式操作文档,现在讨论数据序列化格式 CSV、JSON 和 XML。
第十九章:时间管理、任务调度和程序启动 介绍了 Python 程序如何处理时间和日期,以及如何安排您的计算机在特定时间执行任务。还展示了您的 Python 程序如何启动非 Python 程序。
第二十章:发送电子邮件、短信和推送通知 介绍了如何编写程序,通过电子邮件或移动通信通知您,或将这些消息发送给其他人。
第二十一章:制作图表和图像处理 介绍了如何通过编程方式处理图像,例如 JPEG 或 PNG 文件,以及如何使用 Matplotlib 图形制作库。
第二十二章:图像中的文本识别 涵盖了如何使用 PyTesseract 包从图像和扫描文档中提取文本,以便进行进一步处理。
第二十三章:控制键盘和鼠标 介绍了如何通过编程方式控制鼠标和键盘来自动化点击和按键。
第二十四章:文本到语音和语音识别引擎 涵盖了如何使用高级计算机科学包不仅可以从文本生成语音,还可以将语音转换为文本。
您可以在此书的示例中下载源代码和其他资源,网址为nostarch.com/automate-boring-stuff-python-3rd-edition。要查看本书许多程序的实际运行情况,请访问autbor.com/3。
下载和安装 Python
您可以免费从python.org/downloads下载 Windows、macOS 和 Ubuntu Linux 的 Python。
下载页面会检测您的操作系统并推荐适合您电脑的下载包。对于 Android 和 iOS 移动操作系统,存在非官方的 Python 安装程序,但这些超出了本书的范围。Windows、macOS 和 Linux 也有自己的安装选项。下载适合您操作系统的安装程序,并运行程序以安装 Python 解释器软件。
Python 或操作系统的新版本可能会改变安装 Python 所需的步骤。如果您遇到困难,可以咨询autbor.com/install/以获取最新的说明。
下载和安装 Mu
虽然Python 解释器是运行您的 Python 程序的软件,但Mu 编辑器软件是您输入程序的地方,就像在文字处理程序中输入文本一样。您可以从codewith.mu下载 Mu。
在 Windows 和 macOS 上,下载适用于您操作系统的安装程序,然后通过双击安装文件来运行它。如果您在 macOS 上,运行安装程序将打开一个窗口,您必须将 Mu 图标拖到应用程序文件夹图标上以继续安装。如果您在 Ubuntu 上,您需要将 Mu 作为 Python 包安装。在这种情况下,点击下载页面 Python 包部分中的 说明 按钮。
开始使用 Mu
安装完成后,您就可以开始使用 Mu:
-
在 Windows 上,点击屏幕左下角的开始图标,在搜索框中输入 Mu 编辑器,然后选择它。
-
在 macOS 上,打开 Finder 窗口,点击 应用程序,然后点击 mu-editor。您也可以通过 Spotlight 运行 Mu 编辑器。
-
在 Ubuntu 上,选择 应用程序附件终端,然后输入 python3 –m mu。
第一次运行 Mu 时,将出现一个选择模式窗口,其中包含 Adafruit CircuitPython、BBC micro:bit、Pygame Zero、Python 3 等选项。选择 Python 3。您可以在稍后通过点击编辑器窗口顶部的 模式 按钮来更改模式。
开始 IDLE
本书使用 Mu 作为编辑器和交互式 Shell。然而,您可以使用任意数量的编辑器来编写 Python 代码。与 Python 一起安装的 交互式开发环境 (IDLE) 软件可以作为第二个编辑器,如果您由于某些原因无法安装或运行 Mu。现在让我们启动 IDLE(假设您安装的是 Python 3.13 版本):
-
在 Windows 上,点击屏幕左下角的开始图标,在搜索框中输入 IDLE,然后选择 IDLE (Python 3.13 64-bit)。
-
在 macOS 上,打开 Finder 窗口,点击 应用程序,点击 Python 3.13,然后点击 IDLE 图标。您也可以通过 Spotlight 运行 IDLE。
-
在 Ubuntu 上,选择 应用程序附件终端,然后输入 idle。(您也可能能够点击 Ubuntu 侧边栏底部的 显示应用 并然后点击 IDLE。)
交互式 Shell
当您运行 Mu 时,出现的窗口称为 文件编辑器 窗口。您可以通过点击 REPL 按钮来打开 交互式 Shell。Shell 是一个程序,允许您向计算机输入指令,类似于 macOS 和 Windows 上的终端或命令提示符。Python 的解释器软件将立即运行您在 Python 交互式 Shell 中输入的任何指令。
在 Mu 中,交互式 Shell 是窗口下半部分的一个面板,包含如下文本:
Jupyter QtConsole
Python 3
Type 'copyright', 'credits' or 'license' for more information
IPython -- An enhanced Interactive Python. Type '?' for help.
`In [1]:`
如果您运行 IDLE,交互式 Shell 是首先出现的窗口。它应该大部分是空的,除了看起来像这样的文本:
>>>
In [1]: 和 >>> 被称为提示符。本书中的示例将使用 >>> 提示符来表示交互式外壳,因为它更常见。如果你从终端或命令提示符运行 Python,它们也会使用 >>> 提示符。In [1]: 提示符是由 Jupyter Notebook 发明的,另一个流行的 Python 编辑器。
例如,在提示符旁边将以下内容输入到交互式外壳中:
>>> print('Hello, world!')
在你写下这一行并按下回车键后,交互式外壳应该会显示以下内容作为回应:
>>> print('Hello, world!')
`Hello, world!`
你刚刚给电脑下达了一条指令,它按照你的指示做了!
如何寻找帮助
程序员倾向于通过在互联网上搜索问题的答案来学习。这与许多人习惯的学习方式——通过面对面的老师讲授和解答问题——截然不同。使用互联网作为教室的好处在于,有完整的社区可以帮助你解决问题。确实,你的问题可能已经被回答,并且答案已经在网上等待你去发现。如果你遇到错误信息或代码运行出现困难,你不会是第一个遇到这个问题的人,找到解决方案比你想象的要容易。
例如,让我们故意引发一个错误:在交互式外壳中输入'42' + 3。你现在不需要知道这个指令的含义,但结果应该看起来像这样:
>>> '42' + 3
Traceback (most recent call last): # ❶
File "<pyshell#0>", line 1, in <module>
'42' + 3
TypeError: can only concatenate str (not "int") to str # ❷
>>>
错误信息❷出现是因为 Python 无法理解你的指令。错误信息中的回溯部分❶显示了 Python 遇到困难的具体指令和行号。如果你不确定如何处理特定的错误信息,可以在网上搜索。将“TypeError: can only concatenate str (not "int") to str”(包括引号)输入你喜欢的搜索引擎,你应该会看到大量链接解释错误信息的含义和原因,如图 2 所示。

图 2:错误信息的 Google 搜索结果可能非常有帮助。
你经常会发现,有人和你有相同的问题,而且某个乐于助人的人已经回答了它。没有人能了解编程的方方面面,因此,任何软件开发者的日常工作都包括查找技术问题的答案。
提问智能编程问题
如果你通过在线搜索找不到答案,可以尝试在像 Stack Overflow (stackoverflow.com)或reddit.com/r/learnprogramming上的“学习编程”subreddit 这样的网络论坛中提问。但请记住,有聪明的方式来提问编程问题,这有助于他人帮助你。首先,请确保阅读这些网站上的常见问题解答部分,了解如何正确地发布问题。
当提问编程问题时,请记住以下几点:
-
解释你试图做什么,而不仅仅是你已经做了什么。这可以让你的帮助者知道你是否走错了方向。
-
指定错误发生的位置。是在程序一开始就出现,还是在你执行某些操作后才出现?
-
将整个错误信息和你的代码复制粘贴到
pastebin.com或gist.github.com。这些网站使得在线分享大量代码变得容易,而不会丢失任何文本格式。然后你可以将发布代码的 URL 放在你的电子邮件或论坛帖子中。例如,以下是我发布的一些代码的位置:pastebin.com/2k3LqDsd和gist.github.com/asweigart/6912168。 -
解释你已经尝试过什么来解决你的问题。这告诉人们你已经投入了一些工作来自己解决问题。
-
列出你使用的 Python 版本。也要说明你正在运行的操作系统及其版本。
-
如果你是在修改代码后出现错误,请详细说明你具体做了什么更改。
-
说明你是否每次运行程序都能重现错误,或者错误是否只在执行某些操作后发生。在后一种情况下,也要解释这些操作是什么。
同时也要遵循良好的在线礼仪。例如,不要用大写字母发问,也不要对试图帮助你的人提出不合理的要求。
你可以在autbor.com/help上的博客文章中找到更多关于如何请求编程帮助的信息。我喜欢帮助人们发现 Python。我在inventwithpython.com/blog的博客上写编程教程,你可以通过al@inventwithpython.com与我联系提问,尽管你通过在reddit.com/r/inventwithpython上发问可能会得到更快的回复。
新版第三版
这本全面修订和更新的版本包括了 16 个新的编程项目,以便你可以练习你将学到的技能。你将找到对 SQLite 和关系数据库的完整介绍,使用 Python 的 sqlite3 模块。你将探索如何在 Windows、macOS 和 Linux 上编译 Python 脚本为可执行程序;以及创建命令行程序并运行它们。你还将学习如何使用PyPDF和 PdfMiner 执行 PDF 操作,除了 Selenium 外,使用 Playwright 控制网络浏览器,让你的程序通过文本到语音库进行语音输出,使用 OpenAI 的 Whisper 库从音频和视频文件创建文本转录,使用 PyTesseract 从图像中提取文本,使用 matplotlib 创建图表,等等。
摘要
对于大多数人来说,他们的电脑只是一个电器,而不是工具。但通过学习如何编程,你将获得进入现代世界最强大工具之一的方法,而且你会在过程中感到乐趣。编程不是脑外科手术——业余爱好者进行实验和犯错误是完全可以的。
本书假设你没有任何编程知识,并将教会你很多,但你可能会超出其范围的问题。记住,提出有效的问题和知道如何找到答案是在你的编程旅程中无价的工具。
让我们开始吧!
1 Python 基础

Python 编程语言具有广泛的语言结构、标准库函数和交互式开发环境功能。幸运的是,你可以忽略大部分内容;你只需要学习足够多的知识来编写一些实用的小程序。
然而,要完成这个任务,你需要掌握一些编程概念。就像正在训练的巫师一样,你可能觉得这些概念似乎很繁琐,但经过一些练习,它们将使你能够像挥动魔杖一样指挥你的电脑,并完成令人难以置信的壮举。
本章包含一些示例,鼓励你将代码输入到 交互式外壳,也称为 读取-评估-打印循环 (REPL),它允许你逐个运行(或 执行)Python 指令,并立即显示结果。使用交互式外壳非常适合学习基本 Python 指令的作用,所以当你跟随本书学习时,请尝试一下。你将记得你所做的一切,比仅仅阅读的内容记得更好。
将表达式输入到交互式外壳中
你可以通过启动 Mu 编辑器来运行交互式外壳。本书的引言提供了下载和安装它的设置说明。在 Windows 上,打开开始菜单,输入 Mu,然后打开 Mu 应用程序。在 macOS 上,打开你的应用程序文件夹,双击 Mu。点击 新建 按钮,将一个空文件保存为 blank.py。当你通过点击 运行 按钮或按 F5 运行这个空白文件时,它将打开交互式外壳,该外壳将作为 Mu 编辑器窗口底部的新面板打开。你应该在交互式外壳中看到一个 >>> 提示符。
你也可以从命令行终端(在 macOS 和 Linux 上)或 Windows 终端(在 Windows 上,你也可以使用较旧的命令提示符应用程序)运行交互式外壳。打开这些命令行窗口后,输入 python(在 Windows 上)或 python3(在 macOS 和 Linux 上)。你会看到交互式外壳的相同 >>> 提示符。如果你想运行一个程序,请运行 python 或 python3 后跟程序 .py 文件名,例如 python blank.py。确保你不在 macOS 的终端上运行 python,因为这可能会在某些 macOS 版本上启动较旧的、不兼容的 Python 2.7 版本。你甚至可能会看到一个消息说 WARNING: Python 2.7 is not recommended。退出 2.7 交互式外壳并运行 python3。
在提示符中输入 2 + 2,让 Python 执行一些简单的数学运算。此时 Mu 窗口应该看起来像这样:
>>> 2 + 2
4
>>>
在 Python 中,2 + 2 被称为一个 表达式,这是该语言中最基本的编程指令类型。表达式由 值(例如 2)和 运算符(例如 +)组成,并且它们总能 评估(即,简化)为单个值。这意味着你可以在 Python 代码的任何地方使用表达式,就像使用值一样。
在上一个例子中,2 + 2 被评估为单个值,4。没有操作符的单个值也被视为一个表达式,尽管它只能评估为自身,如下所示:
>>> 2
`2`
Mu 编辑器有一个显示带有类似 In [1]: 提示符的交互式外壳的 REPL 按钮。流行的 Jupyter Notebook 编辑器使用这种类型的交互式外壳。你可以像使用带有 >>> 提示符的正常 Python 交互式外壳一样使用这个交互式外壳。REPLs 并非 Python 独有;许多编程语言也提供 REPLs,以便你可以对其代码进行实验。
编程涉及一些你可能不熟悉的数学运算:
-
幂运算(或 乘方)是重复乘以一个数,就像乘法是重复加一个数一样。例如,2 的四次方(或 2 的四次幂),写作或 2⁴ 或
2 ** 4,是数字 2 乘以自身四次:2⁴ = 2 × 2 × 2 × 2 = 16。 -
模块化算术类似于除法的余数结果。例如,
14 % 4评估为2,因为 14 除以 4 得到 3 余 2。尽管 Python 的取模运算符是%,但模块化算术与百分比无关。 -
整数除法与普通除法相同,只是结果向下取整。例如,
25 / 8是3.125,但25 // 8是3,而29 / 10是2.9,但29 // 10是2。
你可以在 Python 表达式中使用许多其他运算符。例如,表 1-1 列出了 Python 中的所有数学运算符。
表 1-1:数学运算符
| 运算符 | 操作 | 示例 | 评估为 ... |
|---|---|---|---|
** |
幂运算 | 2 ** 3 |
8 |
% |
取模/余数 | 22 % 8 |
6 |
// |
整数除法 | 22 // 8 |
2 |
/ |
除法 | 22 / 8 |
2.75 |
* |
乘法 | 3 * 5 |
15 |
- |
减法 | 5 - 2 |
3 |
+ |
加法 | 2 + 2 |
4 |
Python 数学运算符的运算顺序(也称为优先级)与数学中的类似。**运算符首先评估;*、/、//和%运算符按从左到右的顺序评估;而+和-运算符最后评估(也是从左到右)。如果你需要,可以使用括号来覆盖通常的优先级。在 Python 中,运算符和值之间的空白没有关系,除了行首的缩进。但惯例,或非官方规则,是在运算符和值之间有一个空格。将以下表达式输入到交互式外壳中:
>>> 2 + 3 * 6
20
>>> (2 + 3) * 6
30
>>> 48565878 * 578453
28093077826734
>>> 2 8
256
>>> 23 / 7
3.2857142857142856
>>> 23 // 7
3
>>> 23 % 7
2
>>> 2 + 2
4
>>> (5 - 1) * ((7 + 1) / (3 - 1))
`16.0`
在每种情况下,作为程序员的你都必须输入表达式,但 Python 会完成评估的困难部分。Python 会持续评估表达式的各个部分,直到它成为一个单一值:

这些规则共同构成了 Python 作为编程语言的基本部分,就像帮助我们沟通的语法规则一样。以下是一个例子:
这是一个语法正确的英语句子。
这个句子在语法上不正确,不是英语。
第二行难以解析,因为它不遵循英语的规则。同样,如果你输入了一个错误的 Python 指令,Python 将无法理解它,并将显示一个SyntaxError错误消息,如下所示:
>>> 5 +
File "<python-input-0>", line 1
5 +
^
SyntaxError: invalid syntax
>>> 42 + 5 + * 2
File "<python-input-0>", line 1
42 + 5 + * 2
^
SyntaxError: invalid syntax
你可以通过将其输入到交互式外壳中来始终测试一条指令是否有效。不用担心损坏电脑;最坏的情况是 Python 会响应一个错误消息。专业的软件开发人员在编写代码时经常会收到错误消息。
整数、浮点数和字符串数据类型
记住,表达式只是结合了运算符的值,并且它们总是评估为单个值。一个数据类型是一个值的类别,每个值都属于恰好一个数据类型。Python 中最常见的几种数据类型列于表 1-2 中。例如,-2和30这样的值被称为整数值。整数(或int)数据类型表示的是整数。带有小数点的数字,例如3.14,被称为浮点数(或floats)。请注意,尽管值42是整数,但值42.0将是一个浮点数。程序员通常使用number来指代整数和浮点数,尽管number本身并不是 Python 的数据类型。
Python 中的一个微妙细节是,使用整数和浮点数进行的任何数学运算都会得到一个浮点数,而不是整数。虽然 3 + 4 计算结果为整数 7,但表达式 3 + 4.0 计算结果为浮点数 7.0。使用 / 除法运算符对两个整数进行除法运算也会得到一个浮点数。例如,16 / 4 计算结果为 4.0 而不是 4。大多数情况下,这个信息对你的程序来说并不重要,但了解它将解释为什么你的数字可能会突然出现小数点。
表 1-2:常见数据类型
| 数据类型 | 示例 |
|---|---|
| 整数 (int) | -2, -1, 0, 1, 2, 3, 4, 5 |
| 浮点数 (float) | -1.25, -1.0, -0.5, 0.0, 0.5, 1.0, 1.25 |
| 字符串 (str) | 'a', 'aa', 'aaa', 'Hello!', '11 cats', '5' |
Python 程序也可以有文本值,称为 字符串,或 strs(发音为“stirs”)。始终用单引号 (') 将字符串括起来(如 'Hello' 或 'Goodbye cruel world!'),这样 Python 就知道字符串的开始和结束位置。你甚至可以有一个没有任何字符的字符串,'',称为 空字符串 或 空白字符串。字符串将在第八章中更详细地解释。
你可能会看到错误信息 SyntaxError: unterminated string literal,如下例所示:
>>> 'Hello, world!
`SyntaxError: unterminated string literal (detected at line 1)`
这个错误意味着你可能忘记了字符串末尾的最后一个单引号字符。
字符串连接和复制
运算符的含义可能根据其旁边值的类型而改变。例如,当 + 运算符作用于两个整数或浮点值时,它是加法运算符。然而,当 + 用于组合两个字符串值时,它作为字符串连接运算符。在交互式外壳中输入以下内容:
>>> 'Alice' + 'Bob'
`'AliceBob'`
表达式计算结果为一个单一的、新的字符串值,它结合了两个字符串的文本。然而,如果你尝试在字符串和整数值上使用 + 运算符,Python 将不知道如何处理这种情况,并将显示一个错误信息:
>>> 'Alice' + 42
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
'Alice' + 42
TypeError: can only concatenate str (not "int") to str
错误信息 can only concatenate str (not "int") to str 表示 Python 认为你试图将一个整数连接到字符串 'Alice' 上。你的代码必须显式地将整数转换为字符串,因为 Python 无法自动完成这项操作。(我将在第 14 页的“剖析程序”中解释如何在不同数据类型之间进行转换,其中我们将讨论 [str()](https://docs.python.org/3/library/functions.html#func-str)、[int()](https://docs.python.org/3/library/functions.html#int) 和 [float()](https://docs.python.org/3/library/functions.html#float) 函数。)
* 运算符用于乘以两个整数或浮点值。但当 * 运算符用于一个字符串值和一个整数值时,它变成了 字符串复制 运算符。在交互式壳中输入一个字符串乘以一个数字,以查看这个操作的实际效果:
>>> 'Alice' * 5
`'AliceAliceAliceAliceAlice'`
表达式评估为一个单一的字符串值,该值重复原始字符串的次数等于整数值。字符串复制是一个有用的技巧,但它不像字符串连接那样常用。
* 运算符只能用于两个数值(用于乘法),或者一个字符串值和一个整数值(用于字符串复制)。否则,Python 将只显示一个错误消息,例如以下内容:
>>> 'Alice' * 'Bob'
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
'Alice' * 'Bob'
TypeError: can't multiply sequence by non-int of type 'str'
>>> 'Alice' * 5.0
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
'Alice' * 5.0
TypeError: can't multiply sequence by non-int of type 'float'
Python 不理解这些表达式是有道理的:你不能将两个单词相乘,而且很难将一个任意字符串复制成小数倍数。
表达式、数据类型和运算符现在可能对你来说很抽象,但随着你对这些概念了解得更多,你将能够创建越来越复杂的程序,这些程序可以对来自电子表格、网站、其他程序的输出以及其他地方的数据进行数学运算。
在变量中存储值
一个 变量 就像计算机内存中的一个盒子,你可以在这里存储一个单一的值。如果你想在程序中稍后使用一个已评估表达式的结果,你可以将它保存在一个变量中。
赋值语句
你将通过 赋值语句 将值存储在变量中。赋值语句由一个变量名、一个等号(称为 赋值运算符)以及要存储的值组成。如果你输入赋值语句 spam = 42,一个名为 spam 的变量将存储整数值 42。
你可以将变量想象成一个放置值的标签盒子,但第六章将解释为什么将一个标签附加到值上可能是一个更好的比喻。两者都在图 1-1 中展示。

图 1-1:代码 spam = 42 就像告诉程序,“变量 spam 现在包含整数值 42。”
例如,在交互式壳中输入以下内容:
>>> spam = 40 # ❶
>>> spam
40
>>> eggs = 2
>>> spam + eggs # ❷
42
>>> spam + eggs + spam
82
>>> spam = spam + 2 # ❸
>>> spam
`42`
变量是在第一次存储值时 初始化(或创建)的 ❶。之后,你可以在包含其他变量和值的表达式中使用它 ❷。当一个变量被赋予一个新值 ❸ 时,旧值会被遗忘,这就是为什么在示例的最后,spam 评估为 42 而不是 40 的原因。这被称为 覆盖 变量。在交互式壳中输入以下代码以尝试覆盖一个字符串:
>>> spam = 'Hello'
>>> spam
'Hello'
>>> spam = 'Goodbye'
>>> spam
`'Goodbye'`
就像图 1-1 中的盒子一样,图 1-2 中的 spam 变量存储 'Hello',直到你用 'Goodbye' 替换这个字符串。

图 1-2:当一个新的值被分配给一个变量时,旧的值会被遗忘。
你也可以将覆盖变量视为将名称标签重新分配给新值。
变量名
一个好的变量名描述了它包含的数据。想象一下,你搬到了一个新家,并将所有的搬家箱子都标记为 东西。你永远找不到任何东西!本书的大部分例子(以及 Python 的文档)都使用了像 spam、eggs 和 bacon 这样的通用变量名,这些名字来自蒙提·派森的“Spam”片段。但在你的程序中,描述性的名字将有助于使你的代码更易于阅读。
虽然你可以几乎给变量取任何名字,但 Python 仍然有一些命名限制。你的变量名必须遵守以下四个规则:
-
它不能包含空格。
-
它只能使用字母、数字和下划线字符 (
_)。 -
它不能以数字开头。
-
它不能是 Python 关键字,例如
if、for、return或你在本书中将要学习的其他关键字。
表 1-3 展示了合法变量名的例子。
表 1-3:有效和无效变量名
| 有效变量名 | 无效变量名 |
|---|---|
current_balance |
current-balance (连字符是不允许的) |
currentBalance |
current balance (空格是不允许的) |
account4 |
4account (不能以数字开头) |
_42 |
42 (可以以下划线开头但不能以数字开头) |
TOTAL_SUM |
TOTAL_$UM (特殊字符如 $ 是不允许的) |
hello |
'hello' (特殊字符如 ' 是不允许的) |
变量名是区分大小写的,这意味着 spam、SPAM、Spam 和 sPaM 是四个不同的变量。Python 的惯例是以小写字母开头你的变量:spam 而不是 Spam。
你的第一个程序
虽然交互式外壳适合一次运行一个 Python 指令,但要编写整个 Python 程序,你需要在文件编辑器中输入指令。文件编辑器 与记事本和 TextMate 等文本编辑器类似,但它有一些专门用于输入源代码的功能。要在 Mu 中打开新文件,请点击顶部行中的 新建 按钮。
出现的制表符应该包含等待你输入的光标,但它与交互式外壳不同,交互式外壳会在你按下 ENTER 键后立即运行 Python 指令。文件编辑器允许你输入多个指令,保存文件,并运行程序。以下是区分两者的方法:
-
交互式外壳总是带有
>>>或In [1]:提示。 -
文件编辑器不会有
>>>或In [1]:提示。
现在是时候创建你的第一个程序了!当文件编辑器窗口打开时,输入以下内容:
# This program says hello and asks for my name.
print('Hello, world!')
print('What is your name?') # Ask for their name.
my_name = input('>')
print('It is good to meet you, ' + my_name)
print('The length of your name is:')
print(len(my_name))
print('What is your age?') # Ask for their age.
my_age = input('>')
print('You will be ' + str(int(my_age) + 1) + ' in a year.')
一旦你输入了源代码,保存它,这样你每次启动 Mu 时就无需重新输入。点击 保存,在文件名字段中输入 hello.py,然后点击 保存。
你应该时不时地保存你的程序,这样如果电脑崩溃或你意外退出 Mu,你不会丢失代码。作为一个快捷方式,你可以在 Windows 和 Linux 上按 CTRL-S,或者在 macOS 上按 -S 来保存你的文件。
保存后,让我们运行我们的程序。按 F5 键或点击 运行 按钮。当程序请求输入你的名字时,输入你的名字。程序在交互式壳中的输出应该看起来像这样:
Hello, world!
What is your name?
>Al
It is good to meet you, Al
The length of your name is:
2
What is your age?
>4
You will be 5 in a year.
>>>
当没有更多代码行要执行时,Python 程序 终止;也就是说,它停止运行。(你也可以说 Python 程序 退出。)Mu 编辑器在程序终止后显示 >>> 交互式壳提示符,以防你想输入一些进一步的 Python 代码。
你可以通过点击文件标签上的 X 来关闭文件编辑器,就像关闭浏览器标签一样。要重新加载已保存的程序,从菜单中选择 加载。现在做,在出现的窗口中,选择 hello.py 并点击 打开 按钮。你之前保存的 hello.py 程序应该在文件编辑器窗口中打开。
你可以使用 Python Tutor 可视化工具查看程序的逐步执行。访问 pythontutor.com。点击前进按钮来浏览程序执行的每个步骤。你将能够看到变量值和输出的变化。
程序剖析
在文件编辑器中打开你的新程序后,让我们快速浏览一下它使用的 Python 指令,看看每一行代码做了什么。
注释
以下行被称为 注释:
# This program says hello and asks for my name.
Python 会忽略注释,你可以使用它们来写笔记或提醒自己代码试图做什么。任何在井号(#)后面的行剩余文本都是注释的一部分。
有时候程序员会在代码行前加上一个 # 来在测试程序时临时移除它。这被称为 注释掉 代码,当你试图找出程序为什么不起作用时,这可能会很有用。当你准备好将行放回时,你可以稍后移除 #。
Python 也会忽略注释后的空白行。你可以在程序中添加任意多的空白行。这种间距可以使你的代码更容易阅读,就像书中的段落一样。
print() 函数
[print()](https://docs.python.org/3/library/functions.html#print) 函数会在屏幕上显示其括号内的字符串值:
print('Hello, world!')
print('What is your name?') # Ask for their name.
这行print('Hello, world!')的意思是“打印出字符串'Hello, world!'中的文本。”当 Python 执行这一行时,你说 Python 正在调用print()函数,字符串值正在被传递给函数。传递给函数调用的值是一个参数。注意引号并没有打印到屏幕上。它们只是标记字符串的开始和结束;它们不是字符串值文本的一部分。
注意
你也可以使用这个函数在屏幕上显示一个空白行;在括号中不包含任何内容时调用 print()。
当你编写一个函数名时,末尾的开括号和闭括号标识它为函数的名称。这就是为什么在这本书中,你会看到print()而不是print。在函数名和开括号之间没有空格是一个标准约定,尽管 Python 不要求这样做。第三章将更详细地描述函数。
输入()函数
[input()](https://docs.python.org/3/library/functions.html#input)函数等待用户在键盘上输入一些文本并按下回车键:
my_name = input('>')
这个函数调用评估为与用户文本相同的字符串,其余代码将my_name变量赋值为这个字符串值。传递给函数的'>'字符串会导致>提示符出现,这作为用户需要输入的指示器。你的程序不需要将字符串传递给input()函数;如果你调用input(),程序将等待用户输入文本而不显示任何提示。
你可以将input()函数调用视为一个表达式,其评估结果为用户输入的任何字符串。如果用户输入了'Al',赋值语句实际上就是my_name = 'Al'。
如果你调用input()并看到错误消息,如NameError: name 'Al' is not defined,问题是你正在使用 Python 2 而不是 Python 3 运行代码。
欢迎信息
以下对print()的调用包含在括号中的表达式'It is good to meet you, ' + my_name:
print('It is good to meet you, ' + my_name)
记住,表达式总是可以评估为单个值。如果'Al'是存储在my_name中的值,那么这个表达式评估为'It is good to meet you, Al'。这个单一的字符串值随后传递给print()函数,它在屏幕上打印出来。
len()函数
你可以将字符串值(或包含字符串的变量)传递给[len()](https://docs.python.org/3/library/functions.html#len)函数,该函数评估为该字符串中字符的整数值:
print('The length of your name is:')
print(len(my_name))
在交互式外壳中输入以下内容以尝试此操作:
>>> len('hello')
5
>>> len('My very energetic monster just scarfed nachos.')
46
>>> len('')
`0`
就像那些例子一样,len(my_name) 的结果是一个整数。我们说 len() 函数调用 返回 或 输出 这个整数值,而这个值是函数调用的 返回值。然后它被传递给 print() 来在屏幕上显示。print() 函数允许你传递整数值或字符串值,但请注意,当你将以下内容输入到交互式外壳中时出现的错误:
>>> print('I am ' + 29 + ' years old.')
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
print('I am ' + 29 + ' years old.')
TypeError: can only concatenate str (not "int") to str
导致错误的不是 print() 函数;而是你尝试传递给 print() 的表达式。如果你单独将这个表达式输入到交互式外壳中,你也会得到相同的错误信息:
>>> 'I am ' + 29 + ' years old.'
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
'I am ' + 29 + ' years old.'
TypeError: can only concatenate str (not "int") to str
Python 会给出错误,因为 + 运算符只能用来将两个数字相加或连接两个字符串。你不能将一个整数加到一个字符串上,因为在 Python 中这是不允许的。你可以通过使用整数的字符串版本来修复这个问题,如下一节所述。
str()、int() 和 float() 函数
如果你想要将一个整数(如 29)与一个字符串连接,以便传递给 print(),你需要获取 '29' 这个值,它是 29 的字符串形式。str() 函数可以传递一个整数值,并将返回该整数的字符串形式,如下所示:
>>> str(29)
'29'
>>> print('I am ' + str(29) + ' years old.')
`I am 29 years old.`
因为 str(29) 的结果是 '29',所以表达式 'I am ' + str(29) + ' years old.' 的结果是 'I am ' + '29' + ' years old.',这反过来又等于 'I am 29 years old.'。这是传递给 print() 函数的字符串值。
str()、int() 和 float() 函数将分别评估为传递的值的字符串、整数和浮点数形式。尝试在交互式外壳中使用这些函数转换一些值,并观察会发生什么:
>>> str(0)
'0'
>>> str(-3.14)
'-3.14'
>>> int('42')
42
>>> int('-99')
-99
>>> int(1.25)
1
>>> int(1.99)
1
>>> float('3.14')
3.14
>>> float(10)
`10.0`
之前的例子调用了 str()、int() 和 float() 函数,并将其他数据类型的值传递给它们,以获得这些值的字符串、整数或浮点数形式。
str() 函数在你需要将整数或浮点数连接到字符串时非常方便。int() 函数在你有一个作为字符串值的数字,并想在某些数学运算中使用它时也很有用。例如,input() 函数总是返回一个字符串,即使用户输入了一个数字。在交互式外壳中输入 spam = input('>'),然后当它等待你的文本时输入 101:
>>> spam = input('>')
>101
>>> spam
`'101'`
存储在 spam 中的值不是整数 101,而是字符串 '101'。如果你想使用 spam 中的值进行数学运算,请使用 int() 函数来获取它的整数形式,然后将这个新值存储为变量的新值。如果 spam 是字符串 '101',那么表达式 int(spam) 将评估为整数值 101,赋值语句 spam = int(spam) 等价于 spam = 101:
>>> spam = int(spam)
>>> spam
`101`
现在你应该能够将 spam 变量当作整数而不是字符串来处理:
>>> spam * 10 / 5
`202.0`
注意,如果你向 int() 传递一个它无法评估为整数的值,Python 将显示一个错误消息:
>>> int('99.99')
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
int('99.99')
ValueError: invalid literal for int() with base 10: '99.99'
>>> int('twelve')
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
int('twelve')
ValueError: invalid literal for int() with base 10: 'twelve'
如果你需要将浮点数向下舍入,int() 函数也非常有用:
>>> int(7.7)
7
>>> int(7.7) + 1
8
你在程序的最后三行中使用了 int() 和 str() 函数来获取适当的代码数据类型值:
print('What is your age?') # Ask for their age.
my_age = input('>')
print('You will be ' + str(int(my_age) + 1) + ' in a year.')
my_age 变量包含 input() 返回的值。因为 input() 函数总是返回一个字符串(即使用户输入了一个数字),你可以使用 int(my_age) 代码来返回 my_age 中的字符串的整数值。这个整数值随后在表达式 int(my_age) + 1 中添加到 1。
这个加法的结果被传递给 str() 函数:str(int(my _age) + 1)。返回的字符串值随后与字符串 'You will be ' 和 ' in a year.' 连接,以评估为一个大的字符串值。这个大的字符串最终被传递给 print() 以在屏幕上显示。
假设用户为 my_age 输入了字符串 '4'。评估步骤可能如下所示:

描述
字符串 '4' 被转换为整数,因此你可以向它添加 1。结果是 5。str() 函数将结果转换回字符串,因此你可以将其与第二个字符串 'in a year.' 连接,以创建最终的消息。
type() 函数
整数、浮点数和字符串并不是 Python 中唯一的数据类型。随着你继续学习编程,你可能会遇到其他数据类型的值。你可以始终将这些值传递给 [type()](https://docs.python.org/3/library/functions.html#type) 函数以确定它们的类型。例如,在交互式外壳中输入以下内容:
>>> type(42)
<class 'int'>
>>> type(42.0)
<class 'float'>
>>> type('forty two')
<class 'str'>
>>> name = 'Zophie'
>>> type(name) # The name variable has a value of the string type.
<class 'str'>
>>> type(len(name)) # The len() function returns integer values.
<class 'int'>
你不仅可以向 type() 函数传递任何值,而且(就像任何函数调用一样)你也可以传递任何变量或表达式来确定它评估到的值的类型。type() 函数本身返回值,但尖括号意味着它们在语法上不是有效的 Python 代码;你不能运行像 spam = <class 'str'> 这样的代码。
round() 和 abs() 函数
让我们了解两个更多的 Python 函数,就像 len() 函数一样,接受一个参数并返回一个值。[round()](https://docs.python.org/3/library/functions.html#round) 函数接受一个浮点数并返回最接近的整数。在交互式外壳中输入以下内容:
>>> round(3.14)
3
>>> round(7.7)
8
>>> round(-2.2)
-2
round() 函数还接受一个可选的第二个参数,指定它应该四舍五入到多少位小数。在交互式外壳中输入以下内容:
>>> round(3.14, 1)
3.1
>>> round(7.7777, 3)
7.778
对于半数四舍五入的行为有点奇怪。函数调用 round(3.5) 向上四舍五入到 4,而 round(2.5) 向下四舍五入到 2。对于以 .5 结尾的半数,数字会被四舍五入到最近的偶数整数。这被称为 银行家四舍五入。
[abs()](https://docs.python.org/3/library/functions.html#abs) 函数返回数字参数的绝对值。在数学中,这被定义为从 0 的距离,但我发现把它看作是数字的正形式更容易理解。在交互式外壳中输入以下内容:
>>> abs(25)
25
>>> abs(-25)
25
>>> abs(-3.14)
3.14
>>> abs(0)
0
Python 随带了几种不同的函数,你将在本书中了解它们。本节演示了如何在交互式外壳中实验它们,以查看它们在不同输入下的行为。这是练习你所学的新代码的常见技术。
计算机如何使用二进制数存储数据
现在的 Python 代码已经足够了。在这个阶段,你可能觉得编程几乎像是魔法。计算机是如何知道将 2 + 2 转换为 4 的?答案对于这本书来说太复杂了,但我可以通过讨论二进制数(只有数字 1 和 0 的数)与计算有什么关系来解释幕后发生的一部分事情。
电影中的黑客行为通常涉及屏幕上流动的 1s 和 0s。这看起来神秘而令人印象深刻,但这些 1s 和 0s 究竟意味着什么?答案是二进制是最简单的数制,并且可以用廉价的组件实现计算机硬件。二进制,也称为 二进制数制,可以表示与我们的更熟悉的 十进制数制 相同的所有数字。十进制有 10 个数字,从 0 到 9。表 1-4 显示了十进制和二进制的前 27 个整数。
表 1-4:等效的十进制和二进制数
| 十进制 | 二进制 | 十进制 | 二进制 | 十进制 | 二进制 |
|---|---|---|---|---|---|
0 |
0 |
9 |
1001 |
18 |
10010 |
1 |
1 |
10 |
1010 |
19 |
10011 |
2 |
10 |
11 |
1011 |
20 |
10100 |
3 |
11 |
12 |
1100 |
21 |
10101 |
4 |
100 |
13 |
1101 |
22 |
10110 |
5 |
101 |
14 |
1110 |
23 |
10111 |
6 |
110 |
15 |
1111 |
24 |
11000 |
7 |
111 |
16 |
10000 |
25 |
11001 |
8 |
1000 |
17 |
10001 |
26 |
11010 |
将这些数字系统想象成一个机械里程表,就像图 1-3 中所示的那样。当你达到最后一个数字时,它们都会重置为 0,同时增加下一个数字的值。在十进制中,最后一个数字是 9,在二进制中,最后一个数字是 1。这就是为什么十进制数在 9 之后是 10,在 999 之后是 1000。同样,二进制数在 1 之后是 10,在 111 之后是 1000。然而,二进制中的10并不代表十进制中的十,而是代表二。二进制中的1000也不意味着十进制中的一千,而是代表八。您可以在inventwithpython.com/odometer查看一个交互式的二进制和十进制里程表。

图 1-3:十进制(左)和二进制(右)的机械里程表
使用计算机硬件表示二进制数字比表示十进制数字简单,因为只需要表示两种状态。例如,蓝光光盘和 DVD 在其表面有平滑的* lands和凹槽的 pits*,分别表示是否反射光盘播放器的激光。电路中可以有电流通过或没有电流。这些各种硬件标准都有表示两种不同状态的方法。另一方面,创建足够敏感以可靠精度检测 10 种不同电压级别差异的高质量电子组件将非常昂贵。使用简单的组件更经济,两种二进制状态是最简单的。
这些二进制数字被称为比特,简称bit。一个比特可以表示两个数字,8 比特(或 1 字节)可以表示 2⁸,即 256 个数字,从十进制的 0 到 255 或二进制的 0 到 11111111。这类似于单个十进制数字可以表示 10 个数字(0 到 9),而一个八位十进制数字可以表示 10⁸或 10 亿个数字(0 到 99,999,999)。您计算机上的文件大小是以字节为单位的:
-
一个千字节(KB)等于 2 的 10 次方,或 1,024 字节。
-
一个兆字节(MB)等于 2 的 20 次方,或 1,048,576 字节(或 1,024KB)。
-
一个千兆字节(GB)等于 2 的 30 次方,或 1,073,741,824 字节(或 1,024MB)。
-
一个太字节(TB)等于 2 的 40 次方,或 1,099,511,627,776 字节(或 1,024GB)。
莎士比亚的《罗密欧与朱丽叶》文本大约是 135KB。高分辨率的照片大约是 2MB 到 5MB。电影的大小可以从 1GB 到 50GB 不等,这取决于图片质量和电影长度。然而,硬盘和闪存制造商公然对这些术语的含义撒谎。例如,他们把 TB 称为 1,000,000,000,000 字节而不是 1,099,511,627,776 字节,这样他们就可以把 9.09TB 的硬盘广告宣传为 10TB。
二进制的 1 和 0 不仅可以表示任何整数,还可以表示任何形式的数据。使用称为二进制补码的系统,一个字节可以表示从-128 到 127 的数字,而不是从 0 到 255。分数浮点数可以使用称为IEEE-754的系统在二进制中表示。
文本可以通过为每个字母、标点符号或符号分配一个唯一的数字,以二进制数字的形式存储在计算机上。将文本表示为数字的系统称为编码。最流行的文本编码是 UTF-8。在 UTF-8 中,大写字母A由十进制数 65(或 8 位二进制数 01000001)表示,一个?(问号)由数字 63 表示,数字字符7由数字 55 表示。字符串'Hello'存储为数字 72、101、108、108 和 111。当存储在计算机中时,'Hello'以位流的形式出现:0100100001100101011011000110110001101111。
哇!就像那些黑客电影里一样!
工程师需要发明一种方法来将每种数据形式编码为数字。照片和图像可以被分解成称为像素的二维彩色方格网格。每个像素可以使用三个字节来表示它包含多少红色、绿色和蓝色。 (第二十一章更详细地介绍了图像数据。)但为了简短示例,数字 255、0 和 255 可以表示一个包含最大红色和蓝色但绿色为零的像素,结果是一个紫色像素。
声音是由压缩空气的波组成的,这些波到达我们的耳朵,我们的大脑将其解释为听觉。我们可以绘制这些波随时间变化的强度和频率图。图上的数字可以转换为二进制数字并存储在计算机上,随后控制扬声器重现声音。这是计算机音频工作原理的简化,但描述了数字如何代表贝多芬的第五交响曲。
几个图像的数据与音频数据结合存储成视频。所有形式的信息都可以编码成二进制数字。当然,其中还有很多细节,但这就是 1 和 0 如何代表我们信息时代广泛的数据。
摘要
你可以用计算器计算表达式,或者用文字处理器输入字符串连接。你甚至可以通过复制和粘贴文本轻松地进行字符串复制。但是,表达式及其组成部分——运算符、变量和函数调用——是构成程序的基本构建块。一旦你学会了如何处理这些元素,你将能够指导 Python 为你操作大量数据。
记住本章中介绍的不同类型的运算符(+、-、*、/、//、%和用于数学运算的**,以及用于字符串运算的+和*)和三种数据类型(整数、浮点数和字符串)会有所帮助。
我还介绍了几种不同的函数。print() 和 input() 函数处理简单的文本输出(到屏幕)和输入(从键盘)。len() 函数接受一个字符串,并评估为该字符串中字符数量的整数。str()、int() 和 float() 函数将评估为传递给它们的值的字符串、整数或浮点数形式。round() 函数返回四舍五入后的整数,而 abs() 函数返回参数的绝对值。
在下一章中,你将学习如何告诉 Python 根据它拥有的值来做出智能决策,决定运行什么代码、跳过什么代码以及重复什么代码。这被称为流程控制,它允许你编写能够做出智能决策的程序。
练习问题
- 以下哪些是运算符,哪些是值?
*
'hello'
-88.8
-
/
+
5
- 以下哪个是变量,哪个是字符串?
spam
'spam'
-
列出三种数据类型。
-
表达式由什么组成?所有表达式都做什么?
-
本章介绍了赋值语句,例如
spam=10。表达式和语句之间有什么区别? -
在以下代码运行后,变量
bacon包含什么?
bacon = 20
bacon + 1
- 以下两个表达式应该评估为什么?
'spam' + 'spamspam'
'spam' * 3
-
为什么
eggs是一个有效的变量名,而100则不是? -
可以使用哪三个函数来获取值的整数、浮点数或字符串版本?
-
为什么这个表达式会导致错误?你该如何修复它?
'I eat ' + 99 + ' burritos.'
额外加分:在网上搜索 Python 的 len() 函数文档。它将位于一个标题为“内置函数”的网页上。快速浏览其他 Python 函数列表,查找 bin() 和 hex() 函数的功能,并在交互式外壳中尝试它们。
将表达式输入到交互式外壳中
你可以通过启动 Mu 编辑器来运行交互式外壳。本书的引言部分提供了下载和安装它的设置说明。在 Windows 上,打开开始菜单,输入Mu,然后打开 Mu 应用程序。在 macOS 上,打开你的应用程序文件夹,双击Mu。点击新建按钮,将一个空文件保存为 blank.py。当你通过点击运行按钮或按 F5 运行此空白文件时,它将打开交互式外壳,该外壳将作为 Mu 编辑器窗口底部的新面板打开。你应该在交互式外壳中看到一个 >>> 提示符。
你也可以从命令行终端(在 macOS 和 Linux 上)或 Windows 终端(在 Windows 上,你还可以使用较旧的命令提示符应用程序)运行交互式外壳。打开这些命令行窗口后,输入 python(在 Windows 上)或 python3(在 macOS 和 Linux 上)。你会看到交互式外壳的相同 >>> 提示符。如果你想运行一个程序,请运行 python 或 python3 后跟程序 .py 文件名,例如 python blank.py。确保你不在 macOS 的终端上运行 python,因为这可能在某些版本的 macOS 上启动较旧的、不兼容的 Python 2.7 版本。你甚至可能会看到一个消息说 WARNING: Python 2.7 is not recommended。退出 2.7 交互式外壳并运行 python3。
在提示符下输入 2 + 2,让 Python 执行一些简单的数学运算。Mu 窗口现在应该看起来像这样:
>>> 2 + 2
4
>>>
在 Python 中,2 + 2 被称为一个 表达式,这是该语言中最基本的编程指令类型。表达式由 值(例如 2)和 运算符(例如 +)组成,并且它们总能 评估(即简化)为单个值。这意味着你可以在 Python 代码的任何地方使用表达式,就像你可以使用值一样。
在前面的例子中,2 + 2 被评估为单个值 4。没有运算符的单个值也被视为表达式,尽管它只能评估为自身,如下所示:
>>> 2
`2`
Mu 编辑器有一个显示带有类似 In [1]: 提示符的交互式外壳的 REPL 按钮。流行的 Jupyter Notebook 编辑器也使用这种类型的交互式外壳。你可以像使用带有 >>> 提示符的正常 Python 交互式外壳一样使用这个交互式外壳。REPLs 并非 Python 独有;许多编程语言也提供 REPLs,以便你可以对其代码进行实验。
编程涉及一些你可能不熟悉的数学运算:
-
幂运算(或 乘方)是重复乘以一个数,就像乘法是重复加一个数一样。例如,2 的四次方(或 2 的四次幂),写作 2⁴ 或
2 ** 4,是数字 2 乘以自身四次:2⁴ = 2 × 2 × 2 × 2 = 16。 -
模运算类似于除法的余数结果。例如,
14 % 4评估为2,因为 14 除以 4 得到 3 余 2。尽管 Python 的取模运算符是%,但模运算与百分比无关。 -
整数除法与普通除法相同,只是结果向下取整。例如,
25 / 8是3.125但25 // 8是3,以及29 / 10是2.9但29 // 10是2。
你也可以在 Python 表达式中使用许多其他运算符。例如,表 1-1 列出了 Python 中所有的数学运算符。
表 1-1:数学运算符
| 运算符 | 操作 | 示例 | 评估为 ... |
|---|---|---|---|
** |
幂运算 | 2 ** 3 |
8 |
% |
取模/余数 | 22 % 8 |
6 |
// |
整数除法 | 22 // 8 |
2 |
/ |
除法 | 22 / 8 |
2.75 |
* |
乘法 | 3 * 5 |
15 |
- |
减法 | 5 - 2 |
3 |
+ |
加法 | 2 + 2 |
4 |
Python 数学运算符的运算顺序(也称为优先级)与数学中的类似。**运算符首先被评估;*、/、//和%运算符接下来被评估,从左到右;而+和-运算符最后被评估(也是从左到右)。如果你需要,可以使用括号来覆盖常规的优先级。在 Python 中,运算符和值之间的空白不重要,除了行首的缩进。但惯例,或者说非官方规则,是在运算符和值之间有一个空格。将以下表达式输入到交互式外壳中:
>>> 2 + 3 * 6
20
>>> (2 + 3) * 6
30
>>> 48565878 * 578453
28093077826734
>>> 2 8
256
>>> 23 / 7
3.2857142857142856
>>> 23 // 7
3
>>> 23 % 7
2
>>> 2 + 2
4
>>> (5 - 1) * ((7 + 1) / (3 - 1))
`16.0`
在每种情况下,作为程序员的你都必须输入表达式,但 Python 会完成评估的困难部分。Python 会持续评估表达式的各个部分,直到它成为一个单一值:

这些规则共同构成了 Python 作为编程语言的基本部分,就像帮助我们沟通的语法规则一样。以下是一个例子:
这是一个语法正确的英语句子。
这个句子在语法上不正确,不是英语。
第二行难以解析,因为它不符合英语的规则。同样,如果你输入了一个错误的 Python 指令,Python 将无法理解它,并会显示一个SyntaxError错误信息,如下所示:
>>> 5 +
File "<python-input-0>", line 1
5 +
^
SyntaxError: invalid syntax
>>> 42 + 5 + * 2
File "<python-input-0>", line 1
42 + 5 + * 2
^
SyntaxError: invalid syntax
你可以通过将其输入到交互式外壳中始终测试一个指令是否有效。不用担心会损坏电脑;最坏的情况是 Python 会响应一个错误信息。专业的软件开发者在编写代码时经常会收到错误信息。
整数、浮点数和字符串数据类型
记住,表达式只是通过运算符组合的值,它们总是评估为单个值。数据类型是值的类别,每个值都属于恰好一个数据类型。Python 中最常见的几种数据类型列在表 1-2 中。例如,-2和30被认为是整数值。整数(或int)数据类型表示整数。带有小数点的数字,如3.14,称为浮点数(或floats)。请注意,尽管值42是整数,但值42.0会被视为浮点数。程序员通常使用number来指代整数和浮点数的集合,尽管number本身不是 Python 的数据类型。
关于 Python 的一个微妙细节是,使用整数和浮点数进行的任何数学运算都会得到浮点数,而不是整数。虽然3 + 4评估为整数7,但表达式3 + 4.0评估为浮点数7.0。两个整数之间的除法运算符/的结果也是一个浮点数。例如,16 / 4评估为4.0而不是4。大多数情况下,这个信息对你的程序来说并不重要,但了解它将解释为什么你的数字可能会突然出现小数点。
表 1-2:常见数据类型
| 数据类型 | 示例 |
|---|---|
| 整数 (int) | -2, -1, 0, 1, 2, 3, 4, 5 |
| 浮点数 (float) | -1.25, -1.0, -0.5, 0.0, 0.5, 1.0, 1.25 |
| 字符串 (str) | 'a', 'aa', 'aaa', 'Hello!', '11 cats', '5' |
Python 程序也可以有称为字符串的文本值,或strs(发音为“stirs”)。始终用单引号(')字符包围你的字符串(如'Hello'或'Goodbye cruel world!'),这样 Python 就知道字符串的开始和结束位置。你甚至可以有一个没有任何字符的字符串,'',称为空字符串或空字符串。字符串在第八章中有更详细的解释。
你可能会看到错误信息 SyntaxError: unterminated string literal,例如在这个例子中:
>>> 'Hello, world!
`SyntaxError: unterminated string literal (detected at line 1)`
这个错误意味着你可能忘记了字符串末尾的单引号字符。
字符串连接和复制
运算符的含义可能根据其旁边值的类型而改变。例如,当+运算符作用于两个整数或浮点值时,它是加法运算符。然而,当+用于组合两个字符串值时,它作为字符串连接运算符。在交互式外壳中输入以下内容:
>>> 'Alice' + 'Bob'
`'AliceBob'`
表达式计算结果为一个单一的新字符串值,该值结合了两个字符串的文本。然而,如果你尝试在字符串和整数值上使用+运算符,Python 将不知道如何处理,并将显示错误信息:
>>> 'Alice' + 42
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
'Alice' + 42
TypeError: can only concatenate str (not "int") to str
错误信息can only concatenate str (not "int") to str表示 Python 认为你正在尝试将一个整数连接到字符串'Alice'上。你的代码必须显式地将整数转换为字符串,因为 Python 无法自动完成这项操作。(我将在第 14 页的“剖析程序”中解释如何在数据类型之间进行转换,其中我们讨论了[str()](https://docs.python.org/3/library/functions.html#func-str)、[int()](https://docs.python.org/3/library/functions.html#int)和[float()](https://docs.python.org/3/library/functions.html#float)函数。)
*运算符用于乘以两个整数或浮点数。但当*运算符用于一个字符串值和一个整数值时,它变成了字符串复制运算符。在交互式外壳中输入一个字符串乘以一个数字,可以看到这个操作的实际效果:
>>> 'Alice' * 5
`'AliceAliceAliceAliceAlice'`
表达式计算结果为一个字符串值,该值重复原始字符串,重复次数等于整数值。字符串复制是一个有用的技巧,但它不如字符串连接常用。
*运算符只能用于两个数值(用于乘法),或者一个字符串值和一个整数值(用于字符串复制)。否则,Python 将只显示错误信息,例如以下内容:
>>> 'Alice' * 'Bob'
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
'Alice' * 'Bob'
TypeError: can't multiply sequence by non-int of type 'str'
>>> 'Alice' * 5.0
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
'Alice' * 5.0
TypeError: can't multiply sequence by non-int of type 'float'
Python 不理解这些表达式是有道理的:你不能将两个单词相乘,也很难将一个任意字符串以分数次数复制。
表达式、数据类型和运算符现在可能对你来说很抽象,但随着你对这些概念了解得更多,你将能够创建越来越复杂的程序,这些程序可以对来自电子表格、网站、其他程序的输出以及其他地方的数据进行数学运算。
变量中的值存储
变量就像计算机内存中的一个盒子,你可以在这里存储单个值。如果你想在你程序中稍后使用评估过的表达式的结果,你可以将其保存在变量中。
赋值语句
你可以使用赋值语句将值存储在变量中。赋值语句由一个变量名、一个等号(称为赋值运算符)和要存储的值组成。如果你输入赋值语句spam = 42,一个名为spam的变量将存储整数值42。
你可以将变量想象成一个放置值的标签箱,但第六章解释了将标签附加到值上可能是一个更好的隐喻。两者都在图 1-1 中展示。

图 1-1:代码 spam = 42 就像告诉程序,“变量 spam 现在包含整数值 42。”
例如,在交互式外壳中输入以下内容:
>>> spam = 40 # ❶
>>> spam
40
>>> eggs = 2
>>> spam + eggs # ❷
42
>>> spam + eggs + spam
82
>>> spam = spam + 2 # ❸
>>> spam
`42`
变量在第一次存储值时被 初始化(或创建)❶。之后,你可以在包含其他变量和值的表达式中使用它❷。当变量被赋予新值❸时,旧值会被遗忘,这就是为什么在示例的末尾 spam 评估为 42 而不是 40 的原因。这被称为 覆盖 变量。将以下代码输入到交互式外壳中尝试覆盖一个字符串:
>>> spam = 'Hello'
>>> spam
'Hello'
>>> spam = 'Goodbye'
>>> spam
`'Goodbye'`
就像图 1-1 中的盒子一样,图 1-2 中的 spam 变量存储 'Hello',直到你用 'Goodbye' 替换字符串。

图 1-2:当将新值赋给变量时,旧值会被遗忘。
你也可以将覆盖变量视为将名称标签重新分配给新值。
变量名
一个好的变量名能够描述它包含的数据。想象一下,你搬到了一个新家,并将所有的搬家箱子都标记为 物品。你将永远找不到任何东西!本书的大部分示例(以及 Python 的文档)都使用了通用的变量名,如 spam、eggs 和 bacon,这些来自蒙提·派森的“Spam”片段。但在你的程序中,描述性的名称将有助于使你的代码更易于阅读。
虽然你可以几乎用任何名字来命名你的变量,但 Python 仍然有一些命名限制。你的变量名必须遵守以下四条规则:
-
它不能包含空格。
-
它只能使用字母、数字和下划线字符(
_)。 -
它不能以数字开头。
-
它不能是 Python 的 关键字,例如
if、for、return或本书中你将学习的其他关键字。
表 1-3 展示了合法变量名的示例。
表 1-3:合法和非法变量名
| 合法的变量名 | 非法变量名 |
|---|---|
current_balance |
current-balance(不允许有连字符) |
currentBalance |
current balance(不允许有空格) |
account4 |
4account(不能以数字开头) |
_42 |
42(可以以下划线开头但不能以数字开头) |
TOTAL_SUM |
TOTAL_$UM(不允许有特殊字符,如 $) |
hello |
'hello'(不允许有特殊字符,如 ') |
变量名是区分大小写的,这意味着 spam、SPAM、Spam 和 sPaM 是四个不同的变量。Python 的惯例是使用小写字母作为变量名开头:spam 而不是 Spam。
赋值语句
你将使用赋值语句在变量中存储值。赋值语句由变量名、等号(称为赋值运算符)和要存储的值组成。如果你输入赋值语句spam = 42,一个名为spam的变量将存储整数值42。
你可以将变量想象成一个放置值的标签箱,但第六章将解释为什么将名称标签附加到值可能是一个更好的隐喻。两者都在图 1-1 中显示。

图 1-1:代码spam = 42就像告诉程序,“变量 spam 现在包含整数值 42。”
例如,将以下内容输入到交互式外壳中:
>>> spam = 40 # ❶
>>> spam
40
>>> eggs = 2
>>> spam + eggs # ❷
42
>>> spam + eggs + spam
82
>>> spam = spam + 2 # ❸
>>> spam
`42`
变量在第一次存储值时被初始化(或创建)❶。之后,你可以用它在包含其他变量和值的表达式中使用❷。当变量被赋予新值❸时,旧值会被遗忘,这就是为什么在示例的最后,spam评估为42而不是40的原因。这被称为覆盖变量。将以下代码输入到交互式外壳中尝试覆盖一个字符串:
>>> spam = 'Hello'
>>> spam
'Hello'
>>> spam = 'Goodbye'
>>> spam
`'Goodbye'`
就像图 1-1 中的盒子一样,图 1-2 中的spam变量存储'Hello',直到你用'Goodbye'替换字符串。

图 1-2:当将新值分配给变量时,旧值会被遗忘。
你也可以将覆盖变量视为将名称标签重新分配给新值。
变量名
一个好的变量名可以描述它包含的数据。想象一下,你搬到了一个新家,并将所有的搬家箱子都标记为东西。你永远找不到任何东西!本书的大部分示例(以及 Python 的文档)都使用了通用的变量名,如spam、eggs和bacon,这些来自蒙提·派森的“Spam”片段。但在你的程序中,描述性的名称将有助于使你的代码更易于阅读。
虽然你可以几乎用任何名字命名你的变量,但 Python 确实有一些命名限制。你的变量名必须遵守以下四个规则:
-
它不能包含空格。
-
它只能使用字母、数字和下划线(
_)字符。 -
它不能以数字开头。
-
它不能是 Python 关键字,例如
if、for、return或你将在本书中学到的其他关键字。
表 1-3 显示了合法变量名的示例。
表 1-3:合法和非法变量名
| 合法变量名 | 非法变量名 |
|---|---|
current_balance |
current-balance (不允许使用连字符) |
currentBalance |
current balance (不允许有空格) |
account4 |
4account (不能以数字开头) |
_42 |
42 (可以以下划线开头但不能以数字开头) |
TOTAL_SUM |
TOTAL_$UM (不允许使用特殊字符如 $ ) |
hello |
'hello' (不允许使用特殊字符如 ' ) |
变量名是区分大小写的,这意味着 spam、SPAM、Spam 和 sPaM 是四个不同的变量。Python 的惯例是以小写字母开始你的变量:spam 而不是 Spam。
你的第一个程序
虽然交互式外壳可以逐条运行 Python 指令,但要编写整个 Python 程序,你需要在文件编辑器中输入指令。文件编辑器类似于记事本和 TextMate 这样的文本编辑器,但它有一些专门用于输入源代码的功能。要在 Mu 中打开新文件,请点击顶部行中的新建按钮。
出现的选项卡应该包含一个等待你输入的光标,但它与交互式外壳不同,交互式外壳会在你按下 ENTER 键时立即运行 Python 指令。文件编辑器允许你输入多个指令,保存文件,并运行程序。以下是区分两者的方法:
-
交互式外壳总是带有
>>>或In [1]:提示符。 -
文件编辑器不会有
>>>或In [1]:提示符。
现在是时候创建你的第一个程序了!当文件编辑器窗口打开时,将其输入以下内容:
# This program says hello and asks for my name.
print('Hello, world!')
print('What is your name?') # Ask for their name.
my_name = input('>')
print('It is good to meet you, ' + my_name)
print('The length of your name is:')
print(len(my_name))
print('What is your age?') # Ask for their age.
my_age = input('>')
print('You will be ' + str(int(my_age) + 1) + ' in a year.')
一旦你输入了源代码,保存它以便每次启动 Mu 时无需重新输入。点击保存,在文件名字段中输入hello.py,然后点击保存。
你应该在编写程序时偶尔保存程序。这样,如果电脑崩溃或你意外退出 Mu,你不会丢失代码。作为一个快捷键,你可以在 Windows 和 Linux 上按 CTRL-S,或在 macOS 上按 -S 来保存你的文件。
保存后,让我们运行我们的程序。按 F5 键或点击运行按钮。当程序请求输入你的名字时,输入你的名字。在交互式外壳中程序输出的内容应该看起来像这样:
Hello, world!
What is your name?
>Al
It is good to meet you, Al
The length of your name is:
2
What is your age?
>4
You will be 5 in a year.
>>>
当没有更多的代码行要执行时,Python 程序终止;也就是说,它停止运行。(你也可以说 Python 程序退出。)Mu 编辑器在程序终止后显示 >>> 交互式外壳提示符,以防你想输入一些进一步的 Python 代码。
你可以通过点击文件标签上的X来关闭文件编辑器,就像关闭浏览器标签一样。要重新加载已保存的程序,从菜单中选择加载。现在就做吧,在出现的窗口中,选择hello.py并点击打开按钮。你之前保存的 hello.py 程序应该在文件编辑器窗口中打开。
你可以使用 Python Tutor 可视化工具查看程序的逐步执行过程,该工具位于pythontutor.com。点击前进按钮来移动到程序执行的每个步骤。你将能够看到变量值和输出的变化。
程序剖析
在文件编辑器中打开你的新程序,让我们快速浏览一下它使用的 Python 指令,通过查看每一行代码的作用。
注释
以下行被称为注释:
# This program says hello and asks for my name.
Python 忽略注释,你可以使用它们来写笔记或提醒自己代码试图做什么。任何跟随井号(#)的文本都是注释的一部分。
有时程序员会在代码行前加上#,以在测试程序时临时将其删除。这被称为注释掉代码,当你试图找出程序为什么不起作用时,这可能很有用。当你准备好将行放回时,可以稍后移除#。
Python 也会忽略注释后的空白行。你可以根据需要向程序中添加任意多的空白行。这种间距可以使你的代码更容易阅读,就像书中的段落一样。
print()函数
[print()](https://docs.python.org/3/library/functions.html#print)函数在屏幕上显示其括号内的字符串值:
print('Hello, world!')
print('What is your name?') # Ask for their name.
print('Hello, world!')这一行意味着“打印出字符串'Hello, world!'中的文本。”当 Python 执行这一行时,你可以说 Python 正在调用print()函数,字符串值正被传递给函数。传递给函数调用的值是一个参数。注意,引号不会打印到屏幕上。它们只是标记字符串的开始和结束;它们不是字符串值文本的一部分。
注意
你也可以使用这个函数在屏幕上显示一个空白行;调用 print()时在括号之间不放置任何内容。
当你写一个函数名时,末尾的开括号和闭括号标识它为函数名。这就是为什么在这本书中,你会看到print()而不是print。在函数名和开括号之间没有空格是标准约定,尽管 Python 不要求这样做。第三章将更详细地描述函数。
input()函数
[input()](https://docs.python.org/3/library/functions.html#input)函数等待用户在键盘上输入一些文本并按下 ENTER 键:
my_name = input('>')
这个函数调用将评估为与用户文本相同的字符串,其余的代码将my_name变量赋值为这个字符串值。传递给函数的'>'字符串会导致>提示符出现,这作为用户需要输入内容的指示。你的程序不需要向input()函数传递字符串;如果你调用input(),程序将等待用户输入文本而不显示任何提示。
你可以将 input() 函数调用视为一个表达式,其计算结果为用户输入的任何字符串。如果用户输入了 'Al',则赋值语句实际上会是 my_name = 'Al'。
如果你调用 input() 并看到错误消息,例如 NameError: name 'Al' is not defined,问题是你正在使用 Python 2 而不是 Python 3 运行代码。
欢迎信息
以下对 print() 的调用包含在括号内的表达式 'It is good to meet you, ' + my_name:
print('It is good to meet you, ' + my_name)
记住,表达式总是可以计算出一个单一值。如果 'Al' 是存储在 my_name 中的值,那么这个表达式计算结果为 'It is good to meet you, Al'。这个单一的字符串值随后被传递给 print(),它在屏幕上打印出来。
len() 函数
你可以将 [len()](https://docs.python.org/3/library/functions.html#len) 函数传递一个字符串值(或包含字符串的变量),该函数计算该字符串中字符的整数值:
print('The length of your name is:')
print(len(my_name))
将以下内容输入到交互式外壳以尝试此操作:
>>> len('hello')
5
>>> len('My very energetic monster just scarfed nachos.')
46
>>> len('')
`0`
就像那些例子一样,len(my_name) 计算结果为一个整数。我们说 len() 函数调用 返回 或 输出 这个整数值,这个值就是函数调用的 返回值。然后这个值被传递给 print() 函数,以便在屏幕上显示。print() 函数允许你传递整数或字符串值,但请注意,当你将以下内容输入到交互式外壳时出现的错误:
>>> print('I am ' + 29 + ' years old.')
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
print('I am ' + 29 + ' years old.')
TypeError: can only concatenate str (not "int") to str
print() 函数并没有引起那个错误;而是你尝试传递给 print() 的表达式。如果你单独在交互式外壳中输入这个表达式,你也会得到相同的错误消息:
>>> 'I am ' + 29 + ' years old.'
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
'I am ' + 29 + ' years old.'
TypeError: can only concatenate str (not "int") to str
Python 会报错,因为 + 运算符只能用于将两个数字相加或将两个字符串连接起来。你不能将整数与字符串相加,因为在 Python 中这是不允许的。你可以通过使用整数的字符串形式来修复这个问题,如下一节所述。
str(), int(), 和 float() 函数
如果你想要将整数(如 29)与字符串连接以传递给 print(),你需要获取 '29' 的值,这是 29 的字符串形式。str() 函数可以传递一个整数值,并将返回该整数的字符串形式,如下所示:
>>> str(29)
'29'
>>> print('I am ' + str(29) + ' years old.')
`I am 29 years old.`
因为 str(29) 计算结果为 '29',所以表达式 'I am ' + str(29) + ' years old.' 计算结果为 'I am ' + '29' + ' years old.',这反过来又计算结果为 'I am 29 years old.'。这是传递给 print() 函数的字符串值。
str()、int() 和 float() 函数将分别计算你传递的值的字符串、整数和浮点数形式。尝试在交互式外壳中使用这些函数转换一些值,并观察会发生什么:
>>> str(0)
'0'
>>> str(-3.14)
'-3.14'
>>> int('42')
42
>>> int('-99')
-99
>>> int(1.25)
1
>>> int(1.99)
1
>>> float('3.14')
3.14
>>> float(10)
`10.0`
之前的例子调用了 str(), int(), 和 float() 函数,并将其他数据类型的值传递给它们以获得这些值的字符串、整数或浮点数形式。
当你有一个整数或浮点数并想将其连接到字符串时,str() 函数很方便。如果你有一个作为字符串值的数字并想在某些数学中使用它,int() 函数也很有帮助。例如,input() 函数总是返回一个字符串,即使用户输入了一个数字。在交互式外壳中输入 spam = input('>'),然后输入 101:
>>> spam = input('>')
>101
>>> spam
`'101'`
存储在 spam 中的值不是整数 101,而是字符串 '101'。如果你想用 spam 中的值进行数学运算,请使用 int() 函数获取其整数形式,然后将这个新值存储为变量的新值。如果 spam 是字符串 '101',则表达式 int(spam) 将评估为整数值 101,赋值语句 spam = int(spam) 将等同于 spam = 101:
>>> spam = int(spam)
>>> spam
`101`
现在,你应该能够将 spam 变量当作整数而不是字符串来处理:
>>> spam * 10 / 5
`202.0`
注意,如果你传递给 int() 的值不能评估为整数,Python 将显示一个错误消息:
>>> int('99.99')
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
int('99.99')
ValueError: invalid literal for int() with base 10: '99.99'
>>> int('twelve')
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
int('twelve')
ValueError: invalid literal for int() with base 10: 'twelve'
int() 函数在你需要向下舍入浮点数时也非常有用:
>>> int(7.7)
7
>>> int(7.7) + 1
8
你在程序的最后三行中使用了 int() 和 str() 函数来获取适当的代码数据类型值:
print('What is your age?') # Ask for their age.
my_age = input('>')
print('You will be ' + str(int(my_age) + 1) + ' in a year.')
my_age 变量包含 input() 函数返回的值。因为 input() 函数总是返回一个字符串(即使用户输入了一个数字),所以你可以使用 int(my_age) 代码来返回 my_age 中字符串的整数值。然后,这个整数值被加到表达式 int(my_age) + 1 中的 1 上。
这个加法的结果被传递给 str() 函数:str(int(my _age) + 1)。返回的字符串值随后与字符串 'You will be ' 和 ' in a year.' 连接,以评估为一个大的字符串值。这个大的字符串最终被传递给 print() 函数以在屏幕上显示。
假设用户为 my_age 输入了字符串 '4'。评估步骤看起来可能如下所示:

描述
字符串 '4' 被转换为整数,因此你可以给它加 1。结果是 5。str() 函数将结果转换回字符串,因此你可以将它与第二个字符串 'in a year.' 连接以创建最终的消息。
type() 函数
整数、浮点数和字符串并不是 Python 中唯一的数据类型。随着你继续学习编程,你可能会遇到其他数据类型的值。你总是可以将这些值传递给 [type()](https://docs.python.org/3/library/functions.html#type) 函数来确定它们的类型。例如,在交互式外壳中输入以下内容:
>>> type(42)
<class 'int'>
>>> type(42.0)
<class 'float'>
>>> type('forty two')
<class 'str'>
>>> name = 'Zophie'
>>> type(name) # The name variable has a value of the string type.
<class 'str'>
>>> type(len(name)) # The len() function returns integer values.
<class 'int'>
你不仅可以向 type() 函数传递任何值,而且(就像任何函数调用一样)你也可以传递任何变量或表达式来确定它评估到的值的类型。type() 函数本身返回值,但尖括号意味着它们在语法上不是有效的 Python 代码;你不能运行像 spam = <class 'str'> 这样的代码。
round() 和 abs() 函数
让我们了解两个更多的 Python 函数,就像 len() 函数一样,接受一个参数并返回一个值。[round()](https://docs.python.org/3/library/functions.html#round) 函数接受一个浮点值并返回最接近的整数。在交互式外壳中输入以下内容:
>>> round(3.14)
3
>>> round(7.7)
8
>>> round(-2.2)
-2
round() 函数还接受一个可选的第二个参数,指定它应该四舍五入到多少位小数。在交互式外壳中输入以下内容:
>>> round(3.14, 1)
3.1
>>> round(7.7777, 3)
7.778
对于半数四舍五入的行为有点奇怪。函数调用 round(3.5) 四舍五入到 4,而 round(2.5) 四舍五入到 2。对于以 .5 结尾的半数,数字会被四舍五入到最近的偶数整数。这被称为 银行家四舍五入。
[abs()](https://docs.python.org/3/library/functions.html#abs) 函数返回数字参数的绝对值。在数学中,这被定义为从 0 的距离,但我发现把它看作数字的正形式更容易理解。在交互式外壳中输入以下内容:
>>> abs(25)
25
>>> abs(-25)
25
>>> abs(-3.14)
3.14
>>> abs(0)
0
Python 随带提供了一些不同的函数,你将在本书中了解它们。本节演示了如何在交互式外壳中实验它们,以查看它们在不同输入下的行为。这是练习新学到的代码的常见技术。
注释
以下行被称为 注释:
# This program says hello and asks for my name.
Python 忽略注释,你可以使用它们来写笔记或提醒自己代码试图做什么。任何以井号(#)开始的行后面的文本都是注释的一部分。
有时程序员会在一行代码前加上 # 来在测试程序时临时移除它。这被称为 注释掉 代码,当你试图找出程序为什么不起作用时,这可能很有用。当你准备好将行放回原位时,你可以稍后移除 #。
Python 也会忽略注释后面的空白行。你可以根据需要添加任意多的空白行到你的程序中。这种间距可以使你的代码更容易阅读,就像书中的段落一样。
print() 函数
[print()](https://docs.python.org/3/library/functions.html#print) 函数在屏幕上显示其括号内的字符串值:
print('Hello, world!')
print('What is your name?') # Ask for their name.
print('Hello, world!') 这一行表示“打印出字符串 'Hello, world!' 中的文本。”当 Python 执行这一行时,你可以说 Python 正在 调用 print() 函数,并且字符串值正被 传递 到函数中。传递给函数调用的值是一个 参数。请注意,引号不会打印到屏幕上。它们只是标记字符串的开始和结束;它们不是字符串值文本的一部分。
注意
你也可以使用此函数在屏幕上显示一个空白行;在括号中不输入任何内容调用 print()。
当你编写一个函数名时,末尾的开括号和闭括号标识它为函数的名称。这就是为什么在这本书中,你会看到 print() 而不是 print。在函数名和开括号之间没有空格是标准约定,尽管 Python 不要求这样做。第三章将更详细地描述函数。
input() 函数
[input()](https://docs.python.org/3/library/functions.html#input) 函数等待用户在键盘上输入一些文本并按下 ENTER:
my_name = input('>')
此函数调用评估为与用户文本相同的字符串,其余代码将 my_name 变量赋值为此字符串值。传递给函数的 '>' 字符串会导致 > 提示符出现,这作为用户需要输入的指示器。你的程序不需要将字符串传递给 input() 函数;如果你调用 input(),程序将等待用户的文本而不显示任何提示。
你可以将 input() 函数调用视为一个表达式,其结果为用户输入的任何字符串。如果用户输入了 'Al',则赋值语句实际上为 my_name = 'Al'。
如果你调用 input() 并看到错误信息,例如 NameError: name 'Al' is not defined,问题在于你正在使用 Python 2 而不是 Python 3 运行代码。
欢迎信息
以下对 print() 的调用在括号中包含表达式 'It is good to meet you, ' + my_name:
print('It is good to meet you, ' + my_name)
记住,表达式始终可以评估为单个值。如果 'Al' 是存储在 my_name 中的值,则此表达式评估为 'It is good to meet you, Al'。这个单一的字符串值随后传递给 print(),它在屏幕上打印它。
len() 函数
你可以将字符串值(或包含字符串的变量)传递给 [len()](https://docs.python.org/3/library/functions.html#len) 函数,该函数将评估为该字符串中字符的整数值:
print('The length of your name is:')
print(len(my_name))
将以下内容输入到交互式外壳中尝试:
>>> len('hello')
5
>>> len('My very energetic monster just scarfed nachos.')
46
>>> len('')
`0`
就像那些例子一样,len(my_name) 的结果是一个整数。我们说 len() 函数调用返回或输出这个整数值,这个值就是函数调用的返回值。然后这个值被传递给 print() 函数,以便在屏幕上显示。print() 函数允许你传递整数或字符串值,但请注意,当你将以下内容输入到交互式外壳时出现的错误:
>>> print('I am ' + 29 + ' years old.')
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
print('I am ' + 29 + ' years old.')
TypeError: can only concatenate str (not "int") to str
出错的不是 print() 函数;而是你尝试传递给 print() 的表达式。如果你单独将这个表达式输入到交互式外壳,你也会得到相同的错误信息:
>>> 'I am ' + 29 + ' years old.'
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
'I am ' + 29 + ' years old.'
TypeError: can only concatenate str (not "int") to str
Python 会报错,因为 + 运算符只能用来将两个数字相加或连接两个字符串。你不能将一个整数加到一个字符串上,因为在 Python 中这是不允许的。你可以通过使用整数的字符串版本来修复这个问题,如下一节所述。
str()、int() 和 float() 函数
如果你想要将一个整数(如 29)与一个字符串连接起来传递给 print(),你需要得到 '29' 这个值,它是 29 的字符串形式。str() 函数可以传递一个整数值,并将返回该整数的字符串形式,如下所示:
>>> str(29)
'29'
>>> print('I am ' + str(29) + ' years old.')
`I am 29 years old.`
因为 str(29) 的结果是 '29',所以表达式 'I am ' + str(29) + ' years old.' 的结果是 'I am ' + '29' + ' years old.',这又反过来等于 'I am 29 years old.',这是传递给 print() 函数的字符串值。
str()、int() 和 float() 函数将分别评估为传递的值的字符串、整数和浮点数形式。尝试在交互式外壳中使用这些函数转换一些值,并观察会发生什么:
>>> str(0)
'0'
>>> str(-3.14)
'-3.14'
>>> int('42')
42
>>> int('-99')
-99
>>> int(1.25)
1
>>> int(1.99)
1
>>> float('3.14')
3.14
>>> float(10)
`10.0`
之前的例子调用了 str()、int() 和 float() 函数,并将其他数据类型的值传递给它们,以获得这些值的字符串、整数或浮点数形式。
当你需要将一个整数或浮点数连接到字符串时,str() 函数很有用。如果你有一个数字作为字符串值并想在某些数学中使用它,int() 函数也很有帮助。例如,input() 函数总是返回一个字符串,即使用户输入了一个数字。在交互式外壳中输入 spam = input('>'),然后输入 101:
>>> spam = input('>')
>101
>>> spam
`'101'`
存储在 spam 中的值不是整数 101,而是字符串 '101'。如果你想使用 spam 中的值进行数学运算,请使用 int() 函数来获取它的整数形式,然后将这个新值存储为变量的新值。如果 spam 是字符串 '101',那么表达式 int(spam) 将评估为整数值 101,赋值语句 spam = int(spam) 等价于 spam = 101:
>>> spam = int(spam)
>>> spam
`101`
现在你应该能够将 spam 变量当作整数而不是字符串来处理:
>>> spam * 10 / 5
`202.0`
注意,如果你传递给int()的值不能评估为整数,Python 将显示一个错误消息:
>>> int('99.99')
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
int('99.99')
ValueError: invalid literal for int() with base 10: '99.99'
>>> int('twelve')
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
int('twelve')
ValueError: invalid literal for int() with base 10: 'twelve'
如果你需要将浮点数向下取整,int()函数也很有用:
>>> int(7.7)
7
>>> int(7.7) + 1
8
你在程序的最后三行中使用了int()和str()函数来获取代码所需的数据类型的值:
print('What is your age?') # Ask for their age.
my_age = input('>')
print('You will be ' + str(int(my_age) + 1) + ' in a year.')
my_age变量包含input()函数返回的值。因为input()函数总是返回一个字符串(即使用户输入了一个数字),所以你可以使用int(my_age)代码来返回my_age字符串的整数值。然后,这个整数值被加到表达式int(my_age) + 1中的1上。
这个加法的结果被传递给str()函数:str(int(my_age) + 1)。返回的字符串值随后与字符串'You will be '和' in a year.'连接起来,以评估为一个大的字符串值。这个大的字符串最终被传递给print()以在屏幕上显示。
假设用户为my_age输入了字符串'4'。评估步骤可能如下所示:

描述
字符串'4'被转换为整数,因此你可以向它加1。结果是5。str()函数将结果转换回字符串,因此你可以将它与第二个字符串'in a year.'连接起来,以创建最终的消息。
type()函数
整数、浮点数和字符串并不是 Python 中唯一的数据类型。随着你继续学习编程,你可能会遇到其他数据类型的值。你可以将这些值传递给[type()](https://docs.python.org/3/library/functions.html#type)函数来确定它们的类型。例如,在交互式 shell 中输入以下内容:
>>> type(42)
<class 'int'>
>>> type(42.0)
<class 'float'>
>>> type('forty two')
<class 'str'>
>>> name = 'Zophie'
>>> type(name) # The name variable has a value of the string type.
<class 'str'>
>>> type(len(name)) # The len() function returns integer values.
<class 'int'>
你不仅可以向type()传递任何值,而且(就像任何函数调用一样)你也可以传递任何变量或表达式来确定它评估到的值的类型。type()函数本身返回值,但尖括号意味着它们在语法上不是有效的 Python 代码;你不能运行像spam = <class 'str'>这样的代码。
round()和 abs()函数
让我们了解两个更多的 Python 函数,就像len()函数一样,接受一个参数并返回一个值。[round()](https://docs.python.org/3/library/functions.html#round)函数接受一个浮点数并返回最接近的整数。在交互式 shell 中输入以下内容:
>>> round(3.14)
3
>>> round(7.7)
8
>>> round(-2.2)
-2
round()函数还接受一个可选的第二个参数,指定它应该四舍五入到多少位小数。在交互式 shell 中输入以下内容:
>>> round(3.14, 1)
3.1
>>> round(7.7777, 3)
7.778
对于半数四舍五入的行为有点奇怪。函数调用 round(3.5) 向上舍入到 4,而 round(2.5) 向下舍入到 2。对于以 .5 结尾的半数,数字被舍入到最近的偶数整数。这被称为 银行家舍入法。
[abs()](https://docs.python.org/3/library/functions.html#abs) 函数返回数字参数的绝对值。在数学中,这被定义为从 0 的距离,但我发现把它看作数字的正形式更容易理解。在交互式外壳中输入以下内容:
>>> abs(25)
25
>>> abs(-25)
25
>>> abs(-3.14)
3.14
>>> abs(0)
0
Python 随带了许多不同的函数,你将在本书中了解到它们。本节演示了如何在交互式外壳中实验这些函数,以查看它们在不同输入下的行为。这是练习你所学的新代码的常见技术。
计算机如何使用二进制数字存储数据
现在的 Python 代码已经足够了。在这个时候,你可能会认为编程几乎像是魔法。计算机是如何知道将 2 + 2 转换为 4 的?答案太复杂了,不适合这本书,但我可以通过讨论二进制数(只有数字 1 和 0 的数)与计算有什么关系来解释幕后发生的一部分情况。
电影中的黑客行为通常涉及屏幕上流动的 1 和 0 的流。这看起来神秘而令人印象深刻,但这些 1 和 0 实际上意味着什么?答案是二进制是最简单的数制,它可以利用廉价的组件实现计算机硬件。二进制,也称为 二进制数制,可以表示与我们的更熟悉的 十进制数制 相同的所有数字。十进制有 10 个数字,从 0 到 9。表 1-4 显示了前 27 个整数在十进制和二进制中的表示。
表 1-4:等效的十进制和二进制数字
| 十进制 | 二进制 | 十进制 | 二进制 | 十进制 | 二进制 |
|---|---|---|---|---|---|
0 |
0 |
9 |
1001 |
18 |
10010 |
1 |
1 |
10 |
1010 |
19 |
10011 |
2 |
10 |
11 |
1011 |
20 |
10100 |
3 |
11 |
12 |
1100 |
21 |
10101 |
4 |
100 |
13 |
1101 |
22 |
10110 |
5 |
101 |
14 |
1110 |
23 |
10111 |
6 |
110 |
15 |
1111 |
24 |
11000 |
7 |
111 |
16 |
10000 |
25 |
11001 |
8 |
1000 |
17 |
10001 |
26 |
11010 |
将这些数字系统想象成一个机械里程表,就像图 1-3 中所示。当你到达最后一个数字时,它们都会重置为 0,同时增加下一个数字的值。在十进制中,最后一个数字是 9,在二进制中,最后一个数字是 1。这就是为什么十进制数在 9 之后是 10,在 999 之后是 1000。同样,二进制数在 1 之后是 10,在 111 之后是 1000。然而,二进制中的10并不代表与十进制中的十相同的数量;相反,它代表二。而二进制中的1000并不代表十进制中的一千,而是八。您可以在inventwithpython.com/odometer查看一个交互式的二进制和十进制里程表。

图 1-3:十进制(左)和二进制(右)的机械里程表
使用计算机硬件表示二进制数字比表示十进制数字简单,因为只需要表示两种状态。例如,蓝光光盘和 DVD 在其表面刻有平滑的* lands和凹槽的 pits*,分别表示是否反射光盘播放器的激光。电路中可以有电流通过或没有电流。这些各种硬件标准都有表示两种不同状态的方法。另一方面,创建足够敏感以可靠精度检测 10 种不同电压级别的高质量电子组件将非常昂贵。使用简单的组件更经济,而两种二进制状态是最简单的。
这些二进制数字简称为比特。一个比特可以表示两个数字,8 比特(或 1 字节)可以表示 2 的 8 次方,即 256 个数字,在十进制中从 0 到 255,在二进制中从 0 到 11111111。这类似于一个十进制数字可以表示 10 个数字(0 到 9),而一个八位十进制数字可以表示 10 的 8 次方或 10 亿个数字(0 到 99,999,999)。您电脑上的文件大小是以字节为单位的:
-
千字节(KB)是 2 的 10 次方,或 1,024 字节。
-
兆字节(MB)是 2 的 20 次方,或 1,048,576 字节(或 1,024KB)。
-
吉字节(GB)是 2 的 30 次方,或 1,073,741,824 字节(或 1,024MB)。
-
太字节(TB)是 2 的 40 次方,或 1,099,511,627,776 字节(或 1,024GB)。
莎士比亚的罗密欧与朱丽叶文本大约是 135KB。高分辨率的照片大约是 2MB 到 5MB。电影的大小可以从 1GB 到 50GB 不等,这取决于图片质量和电影长度。然而,硬盘和闪存制造商公然对这些术语的含义撒谎。例如,他们把 TB 称为 1,000,000,000,000 字节,而不是 1,099,511,627,776 字节,这样他们就可以宣传一个 9.09TB 的硬盘为 10TB。
二进制的 1 和 0 不仅能表示任何整数,还能表示任何形式的数据。除了 0 到 255 之外,一个字节可以使用称为二进制补码的系统来表示-128 到 127 的数字。分数浮点数可以使用称为IEEE-754的系统在二进制中表示。
文本可以通过为每个字母、标点符号或符号分配一个唯一的数字来存储在计算机上。将文本表示为数字的系统称为编码。文本最流行的编码是 UTF-8。在 UTF-8 中,大写字母A由十进制数 65(或 8 位二进制数 01000001)表示,一个?(问号)由数字 63 表示,数字字符7由数字 55 表示。字符串'Hello'存储为数字 72、101、108、108 和 111。当存储在计算机上时,'Hello'显示为一系列比特流:0100100001100101011011000110110001101111。
哇!就像那些黑客电影里一样!
工程师需要发明一种方法来将每种数据形式编码成数字。照片和图像可以被分解成称为像素的二维彩色方块网格。每个像素可以使用三个字节来表示它包含多少红色、绿色和蓝色。 (第二十一章更详细地介绍了图像数据。) 但为了简短示例,数字 255、0 和 255 可以表示一个含有最大红色和蓝色但绿色为零的像素,结果是一个紫色像素。
声音是由压缩空气的波组成的,这些波到达我们的耳朵,我们的大脑将其解释为听觉。我们可以绘制这些波随时间变化的强度和频率图。图上的数字可以转换成二进制数并存储在计算机上,随后控制扬声器重现声音。这是计算机音频工作原理的简化,但描述了数字如何代表贝多芬的第五交响曲。
几个图像的数据与音频数据结合存储成视频。所有形式的信息都可以编码成二进制数。当然,其中还有很多细节,但这就是 1 和 0 如何代表我们信息时代广泛的数据。
摘要
你可以用计算器计算表达式,或者用文字处理器输入字符串连接。你甚至可以通过复制粘贴文本轻松地进行字符串复制。但是,表达式及其组成部分——运算符、变量和函数调用——是构成程序的基本构建块。一旦你学会了如何处理这些元素,你就能指导 Python 为你处理大量数据。
记住本章中介绍的不同类型的运算符(+、-、*、/、//、%和**用于数学运算,以及+和*用于字符串运算)和三种数据类型(整数、浮点数和字符串)会有所帮助。
我还介绍了一些不同的函数。print()和input()函数处理简单的文本输出(到屏幕)和输入(从键盘)。len()函数接受一个字符串,并评估为字符串中字符数量的整数。str()、int()和float()函数将评估为传递给它们的值的字符串、整数或浮点数形式。round()函数返回四舍五入的整数,而abs()函数返回参数的绝对值。
在下一章中,你将学习如何告诉 Python 根据它拥有的值来智能地决定运行什么代码、跳过什么代码以及重复什么代码。这被称为流程控制,它允许你编写能够做出智能决策的程序。
实践问题
- 以下哪些是运算符,哪些是值?
*
'hello'
-88.8
-
/
+
5
- 以下哪个是变量,哪个是字符串?
spam
'spam'
-
列出三种数据类型。
-
表达式由什么组成?所有表达式都做什么?
-
本章介绍了赋值语句,例如
spam=10。表达式和语句之间有什么区别? -
在以下代码运行后,变量
bacon包含什么?
bacon = 20
bacon + 1
- 以下两个表达式应该评估为什么?
'spam' + 'spamspam'
'spam' * 3
-
为什么
eggs是一个有效的变量名,而100是无效的? -
可以使用哪些函数来获取值的整数、浮点数或字符串版本?
-
为什么这个表达式会导致错误?你该如何修复它?
'I eat ' + 99 + ' burritos.'
额外加分:在网上搜索 Python 的len()函数文档。它将位于一个标题为“内置函数”的网页上。浏览其他 Python 函数的列表,查找bin()和hex()函数的功能,并在交互式 shell 中尝试它们。
2 IF-ELSE 和 流程控制

所以,你已经了解了单个指令的基础知识,也知道一个程序只是一系列这样的指令。但编程的真正优势并不仅仅是像周末的杂事清单一样依次执行指令。根据表达式的评估结果,程序可以决定跳过指令、重复它们或选择执行几个指令中的一个。实际上,你几乎不希望你的程序从代码的第一行开始,并简单地执行每一行,一直到最后。流程控制语句可以根据条件决定执行哪些 Python 指令。
这些流程控制语句直接对应于流程图中的符号,因此我将提供本章讨论的代码的流程图版本。图 2-1 显示了下雨时应该做什么的流程图。按照从开始到结束的箭头路径进行。

图 2-1:一个流程图,告诉你如果下雨时该做什么 描述
在流程图中,通常有不止一种从开始到结束的路径。在计算机程序中的代码行也是这样。流程图用菱形来表示这些分支点,其他步骤用矩形表示,开始和结束步骤用圆角矩形表示。
在学习流程控制语句之前,你首先需要了解如何表示那些是和否的选项,并且你需要理解如何将这些分支点用 Python 代码来编写。为此,让我们来探讨布尔值、比较运算符和布尔运算符。
布尔值
虽然整数、浮点数和字符串数据类型有无数的可能值,但布尔数据类型只有两个值:True和False。(布尔被大写,因为数据类型是以数学家乔治·布尔的名字命名的。)当作为 Python 代码输入时,布尔值True和False没有你围绕字符串放置的引号,并且它们总是以大写字母T或F开头,其余单词以小写字母开头。请注意,这些布尔值没有引号,因为它们与字符串值'True'和'False'不同。在交互式外壳中输入以下内容:
>>> spam = True # ❶
>>> spam
True
>>> true # ❷
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
NameError: name 'true' is not defined
>>> False = 2 + 2 # ❸
File "<python-input-0>", line 1, in <module>
SyntaxError: can't assign to False
其中一些指令是故意错误的,它们会导致错误信息出现。像任何其他值一样,你可以在表达式中使用布尔值并将它们存储在变量中 ❶。如果你没有使用正确的首字母大写 ❷,或者如果你尝试将True和False用作变量名 ❸,Python 会给你一个错误信息。
比较运算符
比较运算符,也称为 关系运算符,比较两个值并评估为单个布尔值。表 2-1 列出了比较运算符。
表 2-1:比较运算符
| 运算符 | 含义 | 示例 |
|---|---|---|
== |
等于 | 5 == 5 评估为 True。 4 == 2 + 2 评估为 True。 |
!= |
不等于 | 1 != 2 评估为 True。 'Hello' != 'Hello' 评估为 False。 |
< |
小于 | 10 < 5 评估为 False。 1.999 < 5 评估为 True。 |
> |
大于 | 1 + 1 > 4 + 8 评估为 False。 99 > 4 + 8 评估为 True。 |
<= |
小于或等于 | 4 <= 5 评估为 True。 5 <= 5 评估为 True。 |
>= |
大于或等于 | 5 >= 4 评估为 True。 5 >= 5 评估为 True。 |
这些运算符根据您提供的值评估为 True 或 False。现在让我们尝试一些运算符,从 == 和 != 开始:
>>> 42 == 42
True
>>> 42 == 99
False
>>> 2 != 3
True
>>> 2 != 2
False
如您所预期,==(等于)当两边的值相同时评估为 True,而 !=(不等于)当两个值不同时评估为 True。== 和 != 运算符实际上可以与任何数据类型的值一起使用:
>>> 'hello' == 'hello'
True
>>> 'hello' == 'Hello'
False
>>> 'dog' != 'cat'
True
>>> True == True
True
>>> True != False
True
>>> 42 == 42.0
True
>>> 42 == '42' # ❶
False
注意,整数或浮点值永远不会等于字符串值。表达式 42 == '42' ❶ 评估为 False,因为 Python 认为 42 这个整数与字符串 '42' 不同。然而,Python 认为 42 这个整数与浮点数 42.0 相同。
与 <、>、<= 和 >= 运算符相反,这些运算符仅适用于整数和浮点值:
>>> 42 < 100
True
>>> 42 > 100
False
>>> 42 < 42
False
>>> eggs = 42
>>> eggs <= 42 # ❶
True
>>> my_age = 29
>>> my_age >= 10 # ❷
True
您通常会使用比较运算符来比较一个变量的值与某个其他值,例如在 eggs <= 42 ❶ 和 my_age >= 10 ❷ 的例子中,或者比较两个变量中的值。 (毕竟,比较两个字面值,如 'dog' != 'cat' 总是会有相同的结果。) 当您学习到流程控制语句时,您将看到更多此类示例。
布尔运算符
三个 布尔运算符(and、or 和 not)用于比较布尔值。像比较运算符一样,它们将这些表达式评估为布尔值。让我们详细探讨这些运算符,从 and 运算符开始。
and 运算符始终接受两个布尔值(或表达式),因此它被认为是一个 二进制 布尔运算符。如果两个布尔值都是 True,则 and 运算符将表达式评估为 True;否则,它评估为 False。在交互式外壳中输入一些使用 and 的表达式,以查看其作用:
>>> True and True
True
>>> True and False
False
真值表显示了布尔运算符的每个可能结果。表 2-2 是 and 运算符的真值表。
表 2-2:and 运算符的真值表
| 表达式 | 评估为 ... |
|---|---|
True and True |
True |
True and False |
False |
False and True |
False |
False and False |
False |
与and运算符一样,or运算符也始终接受两个布尔值(或表达式),因此被认为是二进制布尔运算符。然而,如果两个布尔值中的任何一个为True,or运算符就会将表达式评估为True。如果两个都是False,它就评估为False:
>>> False or True
True
>>> False or False
False
你可以在表 2-3 中看到or运算符的每个可能的结果。
表 2-3:or运算符的真值表
| 表达式 | 评估为... |
|---|---|
True or True |
True |
True or False |
True |
False or True |
True |
False or False |
False |
与and和or不同,not运算符只作用于一个布尔值(或表达式)。这使得它成为一个一元运算符。not运算符简单地评估为相反的布尔值:
>>> not True
False
>>> not not not not True # ❶
True
与在口语和写作中使用双重否定一样,你可以在表达式中使用多个not运算符❶,尽管在实际程序中这样做通常没有理由。表 2-4 显示了not运算符的真值表。
表 2-4:not运算符的真值表
| 表达式 | 评估为... |
|---|---|
not True |
False |
not False |
True |
混合布尔和比较运算符
由于比较运算符评估为布尔值,因此你可以在使用布尔运算符的表达式中使用它们。
记住,and、or和not运算符被称为布尔运算符,因为它们始终作用于布尔值True和False。虽然像4 < 5这样的表达式不是布尔值,但它们是评估到最后为布尔值的表达式。尝试在交互式外壳中输入一些使用比较运算符的布尔表达式:
>>> (4 < 5) and (5 < 6)
True
>>> (4 < 5) and (9 < 6)
False
>>> (1 == 2) or (2 == 2)
True
计算机首先评估左边的表达式,然后评估右边的表达式。当它知道每个表达式的布尔值时,它将整个表达式评估为单个布尔值。你可以将计算机对(4 < 5) and (5 < 6)的评估过程想象成以下:

描述
你也可以在表达式中使用多个布尔运算符,包括比较运算符:
>>> spam = 4
>>> 2 + 2 == spam and not 2 + 2 == (spam + 1) and 2 * 2 == 2 + 2
True
布尔运算符的运算顺序与数学运算符一样。在评估任何数学和比较运算符之后,Python 首先评估not运算符,然后是and运算符,最后是or运算符。
流程控制组件
流程控制语句通常以一个称为条件的部分开始,并且总是跟随一个称为子句的代码块。在你了解 Python 的具体流程控制语句之前,我将介绍条件和块的概念。
条件
你之前看到的布尔表达式都可以被认为是条件,它们与表达式相同;条件只是在流程控制语句的上下文中更具体的名称。条件总是评估为布尔值,True 或 False。流程控制语句根据其条件是 True 还是 False 来决定做什么,几乎每个流程控制语句都使用条件。你将经常编写可以用英语描述的代码,如下所示:“如果这个条件为真,做这件事,否则做另一件事。”你将编写的其他代码与说“只要这个条件继续为真,就重复这些指令”相同。
代码块
Python 代码的行可以被分组到 代码块 中。你可以通过查看代码行的缩进来判断代码块的开始和结束。代码块有四个规则:
-
当缩进增加时,一个新的代码块开始。
-
代码块可以包含其他代码块。
-
当缩进减少到零或到包含代码块的缩进时,代码块结束。
-
Python 预期在以冒号结束的任何语句之后立即有一个新代码块。
通过查看一些缩进的代码,代码块更容易理解,所以让我们在一个小型程序的部分中找到代码块,如下所示:
username = 'Mary'
password = 'swordfish'
if username == 'Mary':
print('Hello, Mary') # ❶
if password == 'swordfish':
print('Hello, Mary') # ❶
else:
print('Hello, Mary') # ❶
第一个代码块❶从行print('Hello, Mary')开始,包含其后的所有行。在这个代码块内部还有一个代码块❷,它只包含一行:print('Access granted.')。第三个代码块❸也是一行长:print('Wrong password.')。
程序执行
在上一章的 hello.py 程序中,Python 从程序顶部开始逐行执行指令。程序执行(或简称,执行)是指当前正在执行的指令。如果你在执行每一行时将手指放在屏幕上的每一行上,你可以将你的手指视为程序执行。
并非所有程序都是简单地从上到下执行。如果你用手指追踪带有流程控制语句的程序,你可能会发现你的手指根据条件在源代码的不同地方跳跃。
流程控制语句
现在我们来探索最重要的流程控制部分:语句本身。这些语句代表你在图 2-1 中的流程图看到的菱形,它们是程序将做出的实际决策。
if
最常见的流程控制语句类型是 [if](https://docs.python.org/3/tutorial/controlflow.html#if-statements) 语句。如果语句的条件为 True,则执行语句的子句(即 if 语句之后的代码块)。如果条件为 False,则跳过子句。
用简单的话说,一个 if 语句可以读作,“如果这个条件为真,执行该子句中的代码。”在 Python 中,一个 if 语句由以下内容组成:
-
if关键字 -
一个条件(即评估为
True或False的表达式) -
一个冒号
-
从下一行开始,一个缩进的代码块(称为
if子句或if块)
例如,假设你有一些检查某人名字是否是 Alice 的代码:
name = 'Alice'
if name == 'Alice':
print('Hi, Alice.')
所有流程控制语句都以冒号结尾,并随后是一个新的代码块(子句)。这个 if 语句的子句是包含 print('Hi, Alice.') 的代码块。图 2-2 显示了这段代码的流程图。

图 2-2:if 语句的流程图描述
尝试将 name 变量更改为除了 'Alice' 之外的其他字符串,然后再次运行程序。注意,“Hi, Alice.” 并没有出现在屏幕上,因为那段代码被跳过了。
else
一个 if 子句可以可选地后面跟一个 else 语句。只有当 if 语句的条件为 False 时,else 子句才会执行。用简单的话说,else 语句可以读作,“如果这个条件为真,执行这段代码。否则,执行那段代码。” else 语句没有条件,并且在代码中,else 语句总是由以下内容组成:
-
else关键字 -
一个冒号
-
从下一行开始,一个缩进的代码块(称为
else子句或else块)
回到 Alice 的例子,让我们看看一些使用 else 语句来提供不同问候语的代码:
name = 'Alice'
if name == 'Alice':
print('Hi, Alice.')
else:
print('Hello, stranger.')
图 2-3 显示了这段代码的流程图。

图 2-3:else 语句的流程图描述
尝试将 name 变量更改为除了 'Alice' 之外的其他字符串,然后重新运行程序。屏幕上不会显示 'Hi, Alice.',而是会显示 'Hello, stranger.'。
elif
当你只想执行其中一个子句时,你会使用 if 或 else。但可能存在你想要执行多个可能子句中的一个的情况。elif 语句是一个“else if”语句,它总是跟在 if 或另一个 elif 语句之后。它提供了一个只有当所有前面的条件都为 False 时才会检查的条件。在代码中,elif 语句总是由以下内容组成:
-
elif关键字 -
一个条件(即评估为
True或False的表达式) -
一个冒号
-
从下一行开始,一个缩进的代码块(称为
elif子句或elif块)
让我们在名字检查器中添加一个 elif 来看看这个语句的实际应用:
name = 'Alice'
age = 33
if name == 'Alice':
print('Hi, Alice.')
elif age < 12:
print('You are not Alice, kiddo.')
这次,程序检查了人的年龄,如果他们小于 12 岁,就会告诉他们不同的事情。你可以在图 2-4 中看到相应的流程图。

图 2-4:elif 语句的流程图 描述
如果 age < 12 是 True 且 name == 'Alice' 是 False,则执行 elif 子句。然而,如果两个条件都是 False,Python 会跳过这两个子句。没有保证至少执行一个子句;在一系列 elif 语句中,只执行一个或一个都不执行。一旦发现某个语句的条件为 True,则自动跳过其余的 elif 子句。例如,打开一个新的文件编辑窗口并输入以下代码,将其保存为 vampire.py:
name = 'Carol'
age = 3000
if name == 'Alice':
print('Hi, Alice.')
elif age < 12:
print('You are not Alice, kiddo.')
elif age > 2000:
print('Unlike you, Alice is not an undead, immortal vampire.')
elif age > 100:
print('You are not Alice, grannie.')
在这里,我添加了两个更多的 elif 语句,以便名字检查器根据 age 迎接不同答案的人。图 2-5 显示了此流程图。

图 2-5:vampire.py 程序中多个 elif 语句的流程图 描述
elif 语句的顺序确实很重要。让我们重新排列它们以引入一个错误。记住,一旦找到 True 条件,就会自动跳过其余的 elif 子句,所以如果你在 vampire.py 中交换一些子句,你会遇到问题。将代码更改为以下内容,并将其保存为 vampire2.py:
name = 'Carol'
age = 3000
if name == 'Alice':
print('Hi, Alice.')
elif age < 12:
print('You are not Alice, kiddo.')
elif age > 100: # ❶
print('You are not Alice, grannie.')
elif age > 2000:
print('Unlike you, Alice is not an undead, immortal vampire.')
假设在这个代码执行之前,age 变量包含的值是 3000。你可能期望代码打印字符串 '不同于你,爱丽丝不是一个不朽的吸血鬼。',然而,因为 age > 100 的条件是 True(毕竟,3,000 确实大于 100)❶,所以打印了字符串 '你不是爱丽丝,奶奶。',并且自动跳过了其余的 elif 语句。记住,最多只能执行一个子句,对于 elif 语句,顺序很重要!
图 2-6 显示了之前代码的流程图。注意 age > 100 和 age > 2000 的菱形是如何交换的。

图 2-6:vampire2.py 程序流程图。X 路径在逻辑上永远不会发生,因为如果年龄大于 2000,它已经大于 100。描述
可选地,你可以在最后一个 elif 语句之后有一个 else 语句。在这种情况下,保证至少会执行一个(并且只有一个)子句。如果每个 if 和 elif 语句的条件都是 False,则执行 else 子句。例如,让我们重新创建 Alice 程序以使用 if、elif 和 else 子句:
name = 'Carol'
age = 3000
if name == 'Alice':
print('Hi, Alice.')
elif age < 12:
print('You are not Alice, kiddo.')
else:
print('You are neither Alice nor a little kid.')
图 2-7 显示了此新代码的流程图,我们将将其保存为 littleKid.py。

图 2-7:littleKid.py 程序的流程图 描述
用简单的英语来说,这种流程控制结构会是这样的:“如果第一个条件为真,执行这个操作。否则,如果第二个条件为真,执行那个操作。否则,执行其他操作。”当你一起使用if、elif和else语句时,记住这些关于如何排序它们的规则以避免像图 2-6 中的错误。首先,总是恰好有一个if语句;你需要任何elif语句都应该跟在if语句后面。其次,如果你想确保至少执行一个子句,用else语句结束结构。
正如你所见,流程控制语句可以使你的程序更加复杂,但也更加精致。不要气馁;随着你编写代码的实践,你会越来越习惯这种复杂性。而且,所有真正的程序员在某个时候都曾花过一小时的时间去发现他们的程序不工作,因为他们不小心输入了<而不是<=。这些小错误每个人都会犯。
简短程序:相反日
在了解了布尔值和if-else语句的知识后,将以下代码输入到一个新文件中,并将其保存为*oppositeday.py*:
today_is_opposite_day = True
# Set say_it_is_opposite_day based on today_is_opposite_day:
if today_is_opposite_day == True: # ❶
say_it_is_opposite_day = True
else:
say_it_is_opposite_day = False
# If it is opposite day, toggle say_it_is_opposite_day:
if today_is_opposite_day == True:
print('Hello, Mary') # ❶
# Say what day it is:
if say_it_is_opposite_day == True:
print('Today is Opposite Day.')
else:
print('Today is not Opposite Day.')
当你运行这个程序时,它会输出'Today is not Opposite Day.'。这段代码中有两个变量。在程序开始时,today_is_opposite_day变量被设置为True。接下来的if语句检查这个变量是否为True(它是),然后设置say_it_is_opposite_day变量为True;否则,它会将变量设置为False。第二个if语句检查today_is_opposite_day是否设置为True(它仍然是),如果是,代码会切换(即设置为相反的布尔值)变量;否则,它会打印出'Today is not Opposite Day.'。
如果你将程序的第一个行改为today_is_opposite_day = False并再次运行程序,它仍然会打印出'Today is not Opposite Day.'。如果我们查看程序,我们可以发现第一个if-else语句将say_it_is_opposite_day设置为False。第二个if语句的条件是False,因此它跳过了其代码块。最后,第三个if语句的条件再次是False,并打印出'Today is not Opposite Day.'
因此,如果今天不是相反日,程序会正确地打印出'Today is not Opposite Day.'。而如果今天是相反日,程序(也是正确地)会打印出'Today is not Opposite Day.',就像在相反日所说的一样。从逻辑上讲,无论变量设置为True还是False,这个程序都不会打印出'Today is Opposite Day.'。实际上,你可以用print('Today is not Opposite Day.')来替换整个程序,它会是相同的程序。这就是为什么程序员不应该按代码行数计费的原因。
简短程序:不诚实容量计算器
在第一章中,我展示了硬盘和闪存制造商如何通过使用 TB 和 GB 的不同定义来夸大他们产品的广告容量。让我们编写一个程序来计算他们的广告容量有多误导。将以下代码输入到一个新文件中,并将其保存为dishonestcapacity.py:
print('Enter TB or GB for the advertised unit:')
unit = input('>')
# Calculate the amount that the advertised capacity lies:
if unit == 'TB' or unit == 'tb':
discrepancy = 1000000000000 / 1099511627776
elif unit == 'GB' or unit == 'gb':
discrepancy = 1000000000 / 1073741824
print('Enter the advertised capacity:')
advertised_capacity = input('>')
advertised_capacity = float(advertised_capacity)
# Calculate the real capacity, round it to the nearest hundredths,
# and convert it to a string so it can be concatenated:
real_capacity = str(round(advertised_capacity * discrepancy, 2))
print('The actual capacity is ' + real_capacity + ' ' + unit)
这个程序要求用户输入硬盘自带的单位,即 TB 或 GB:
# Calculate the amount that the advertised capacity lies:
if unit == 'TB' or unit == 'tb':
discrepancy = 1000000000000 / 1099511627776
elif unit == 'GB' or unit == 'gb':
discrepancy = 1000000000 / 1073741824
TB 比 GB 大,单位越大,广告容量和实际容量之间的差异就越大。if和elif语句使用布尔or运算符,这样无论用户是否以小写或大写输入单位,程序都能正常工作。如果用户输入其他单位,则if子句和elif子句都不会执行,并且discrepancy变量永远不会被赋值。稍后,当程序尝试使用discrepancy变量时,这将会导致错误。我们将在稍后讨论这种情况。
接下来,用户以给定单位输入广告大小:
# Calculate the real capacity, round it to the nearest hundredths,
# and convert it to a string so it can be concatenated:
real_capacity = str(round(advertised_capacity * discrepancy, 2))
我们在这行代码中做了很多事情。让我们以用户输入 10TB 作为广告大小和单位为例。如果我们查看代码行的最内层,我们会看到advertised_capacity被乘以discrepancy。这是实际容量,但它可能有多个数字,例如9.094947017729282。因此,这个数字作为round()函数的第一个参数传递,2作为第二个参数。这个round()函数调用在我们的例子中返回9.09。这是一个浮点数,但我们希望将其转换为字符串形式,以便在下一行代码中将其连接到消息字符串。为此,我们将其传递给str()函数。Python 将这一行代码评估为以下内容:

描述
如果用户没有输入TB、tb、GB或gb作为单位,if和elif语句的条件都将为False,并且discrepancy变量永远不会被创建。但用户直到 Python 尝试使用这个不存在的变量时才会意识到出了问题。Python 会抛出一个NameError: name 'discrepancy' is not defined错误,并指向real_capacity被赋值的行。
然而,这个错误的真正起源是程序没有处理用户输入无效单位的情况。处理这种错误的方法有很多,但最简单的方法是添加一个else子句,显示一条消息,例如“您必须输入 TB 或 GB”,然后调用[sys.exit()](https://docs.python.org/3/library/sys.html#sys.exit)函数来退出程序。(你将在下一章中了解这个函数。)
程序的最后一行通过连接消息字符串到real_capacity和unit字符串来显示实际的硬盘容量:
print('The actual capacity is ' + real_capacity + ' ' + unit)
实际上,硬盘和闪存制造商的谎言更多:我在笔记本电脑中有一个 256GB 的 SD 卡,用于备份。在真实 GB 中,这应该是 274,877,906,944 字节。在虚假 GB 中,应该是 256,000,000,000 字节。但我的电脑报告的实际容量是 255,802,212,352 字节。很奇怪,实际大小总是不准确,总是小于广告中的大小,而不会更大。
概述
通过使用评估为True或False(也称为条件)的表达式,你可以编写能够根据要执行的代码和要跳过的代码做出决定的程序。这些条件是使用==、!=、<、>、<=和>=比较运算符比较两个值的表达式,以评估为布尔值。你还可以使用and、or和not布尔运算符将表达式连接成更复杂的表达式。Python 使用缩进来创建代码块。在本章中,我们使用块作为if、elif和else语句的一部分,但正如你将看到的,还有其他几个 Python 语句也使用块。这些流程控制语句将使你能够编写更智能的程序。
练习题
1. 布尔数据类型有两个值?如何书写它们?
2. 有哪些布尔运算符?
3. 列出每个布尔运算符的真值表(即运算符的每个可能的布尔值组合及其评估结果)。
4. 以下表达式评估为什么?
(5 > 4) and (3 == 5)
not (5 > 4)
(5 > 4) or (3 == 5)
not ((5 > 4) or (3 == 5))
(True and True) and (True == False)
(not False) or (not True)
5. 有哪些比较运算符?
6. 等于运算符和赋值运算符之间的区别是什么?
7. 解释什么是条件以及在哪里使用它。
8. 识别此代码中的三个块:
spam = 0
if spam == 10:
print('eggs')
if spam > 5:
print('bacon')
else:
print('ham')
print('spam')
print('Done')
- 编写代码,如果
spam中存储的是1,则打印Hello;如果spam中存储的是2,则打印Howdy;如果spam中存储的是其他任何内容,则打印Greetings!。
布尔值
虽然整数、浮点数和字符串数据类型有无数的可能值,但布尔数据类型只有两个值:True和False。(布尔首字母大写,因为这种数据类型是以数学家乔治·布尔的名字命名的。)当作为 Python 代码输入时,布尔值True和False没有你围绕字符串放置的引号,并且它们总是以大写字母T或F开头,其余单词以小写字母开头。请注意,这些布尔值没有引号,因为它们与字符串值'True'和'False'不同。在交互式外壳中输入以下内容:
>>> spam = True # ❶
>>> spam
True
>>> true # ❷
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
NameError: name 'true' is not defined
>>> False = 2 + 2 # ❸
File "<python-input-0>", line 1, in <module>
SyntaxError: can't assign to False
一些指令故意是错误的,它们会导致出现错误信息。像任何其他值一样,你可以在表达式中使用布尔值并将它们存储在变量中 ❶。如果你没有使用正确的首字母大写 ❷ 或者如果你尝试将True和False用作变量名 ❸,Python 会给你一个错误信息。
比较运算符
比较运算符,也称为 关系运算符,比较两个值并评估为单个布尔值。表 2-1 列出了比较运算符。
表 2-1:比较运算符
| 运算符 | 含义 | 示例 |
|---|---|---|
== |
等于 | 5 == 5 评估为 True。 4 == 2 + 2 评估为 True。 |
!= |
不等于 | 1 != 2 评估为 True。 'Hello' != 'Hello' 评估为 False。 |
< |
小于 | 10 < 5 评估为 False。 1.999 < 5 评估为 True。 |
> |
大于 | 1 + 1 > 4 + 8 评估为 False。 99 > 4 + 8 评估为 True。 |
<= |
小于或等于 | 4 <= 5 评估为 True。 5 <= 5 评估为 True。 |
>= |
大于或等于 | 5 >= 4 评估为 True。 5 >= 5 评估为 True。 |
这些运算符根据您提供的值评估为 True 或 False。现在让我们尝试一些运算符,从 == 和 != 开始:
>>> 42 == 42
True
>>> 42 == 99
False
>>> 2 != 3
True
>>> 2 != 2
False
如您所预期,==(等于)当两边的值相同时评估为 True,而 !=(不等于)当两个值不同时评估为 True。== 和 != 操作符实际上可以与任何数据类型的值一起使用:
>>> 'hello' == 'hello'
True
>>> 'hello' == 'Hello'
False
>>> 'dog' != 'cat'
True
>>> True == True
True
>>> True != False
True
>>> 42 == 42.0
True
>>> 42 == '42' # ❶
False
注意,整数或浮点值永远不会等于字符串值。表达式 42 == '42' ❶ 评估为 False,因为 Python 认为 42 这个整数与字符串 '42' 不同。然而,Python 认为 42 这个整数与浮点数 42.0 相同。
<、>、<= 和 >= 运算符另一方面,仅适用于整数和浮点值:
>>> 42 < 100
True
>>> 42 > 100
False
>>> 42 < 42
False
>>> eggs = 42
>>> eggs <= 42 # ❶
True
>>> my_age = 29
>>> my_age >= 10 # ❷
True
您通常会使用比较运算符来比较变量的值与某个其他值,例如在 eggs <= 42 ❶ 和 my_age >= 10 ❷ 的示例中,或者比较两个变量中的值。 (毕竟,比较两个文字值,如 'dog' != 'cat' 总是会有相同的结果。)您将在学习流程控制语句时看到更多此类示例。
布尔运算符
三个 布尔运算符(and、or 和 not)用于比较布尔值。像比较运算符一样,它们将这些表达式评估为布尔值。让我们详细探讨这些运算符,从 and 运算符开始。
and 运算符始终接受两个布尔值(或表达式),因此它被认为是一个 二进制 布尔运算符。and 运算符如果两个布尔值都是 True,则评估表达式为 True;否则,评估为 False。在交互式外壳中输入一些使用 and 的表达式,以查看其作用:
>>> True and True
True
>>> True and False
False
真值表显示了布尔运算符的每个可能结果。表 2-2 是 and 运算符的真值表。
表 2-2:and 运算符的真值表
| 表达式 | 评估为 ... |
|---|---|
True and True |
True |
True and False |
False |
False and True |
False |
False and False |
False |
与 and 操作符类似,or 操作符也始终接受两个布尔值(或表达式),因此被视为二元布尔操作符。然而,如果两个布尔值中的任何一个为 True,or 操作符就会将表达式评估为 True。如果两个都是 False,则评估为 False:
>>> False or True
True
>>> False or False
False
你可以在表 2-3 中看到 or 操作符的所有可能结果。
表 2-3:or 操作符的真值表
| 表达式 | 评估为 ... |
|---|---|
True or True |
True |
True or False |
True |
False or True |
True |
False or False |
False |
与 and 和 or 不同,not 操作符只对一个布尔值(或表达式)进行操作。这使得它成为一个 一元 操作符。not 操作符简单地评估为相反的布尔值:
>>> not True
False
>>> not not not not True # ❶
True
就像在口语和写作中使用双重否定一样,你可以使用多个 not 操作符 ❶,尽管在现实程序中这样做没有理由。表 2-4 显示了 not 的真值表。
表 2-4:not 操作符的真值表
| 表达式 | 评估为 ... |
|---|---|
not True |
False |
not False |
True |
混合布尔和比较操作符
由于比较操作符评估为布尔值,因此可以在包含布尔操作符的表达式中使用它们。
回想一下,and、or 和 not 操作符被称为布尔操作符,因为它们始终在布尔值 True 和 False 上操作。虽然像 4 < 5 这样的表达式不是布尔值,但它们是评估为布尔值的表达式。尝试在交互式外壳中输入一些使用比较操作符的布尔表达式:
>>> (4 < 5) and (5 < 6)
True
>>> (4 < 5) and (9 < 6)
False
>>> (1 == 2) or (2 == 2)
True
计算机首先评估左边的表达式,然后评估右边的表达式。当它知道每个表达式的布尔值时,它将整个表达式评估为单个布尔值。你可以将计算机对 (4 < 5) and (5 < 6) 的评估过程想象成以下:

描述
你也可以在表达式中使用多个布尔操作符,包括比较操作符:
>>> spam = 4
>>> 2 + 2 == spam and not 2 + 2 == (spam + 1) and 2 * 2 == 2 + 2
True
布尔操作符的运算顺序与数学操作符类似。在所有数学和比较操作符评估之后,Python 首先评估 not 操作符,然后是 and 操作符,最后是 or 操作符。
流程控制组件
流程控制语句通常以一个称为 条件 的部分开始,并且总是跟随一个称为 子句 的代码块。在你了解 Python 的特定流程控制语句之前,我将介绍条件和块的概念。
条件
你之前见过的布尔表达式都可以被认为是条件,它们与表达式是同一回事;条件 只是在流程控制语句的上下文中更具体的名称。条件总是评估为布尔值,True 或 False。流程控制语句根据其条件是 True 还是 False 来决定做什么,几乎每个流程控制语句都使用条件。你经常会编写可以用英语描述的代码,如下所示:“如果这个条件为真,做这件事,否则做另一件事。”你将编写的其他代码与说“只要这个条件继续为真,就不断重复这些指令”是相同的。
代码块
Python 代码的行可以被分组到 代码块 中。你可以通过查看代码行的缩进来判断代码块的开始和结束。代码块有四个规则:
-
当缩进增加时,新的代码块开始。
-
代码块可以包含其他代码块。
-
当缩进减少到零或到包含代码块的缩进时,代码块结束。
-
Python 预期在任何以冒号结尾的语句之后立即有一个新的代码块。
通过查看一些缩进的代码,代码块更容易理解,所以让我们在下面展示的小程序的一部分中找到代码块:
username = 'Mary'
password = 'swordfish'
if username == 'Mary':
print('Hello, Mary') # ❶
if password == 'swordfish':
print('Hello, Mary') # ❶
else:
print('Hello, Mary') # ❶
第一个代码块 ❶ 从行 print('Hello, Mary') 开始,包含其后的所有行。在这个代码块内部还有一个代码块 ❷,它只包含一行:print('Access granted.')。第三个代码块 ❸ 同样只有一行长:print('Wrong password.')。
程序执行
在上一章的 hello.py 程序中,Python 从程序顶部开始执行指令,逐行向下执行。程序执行(或简称,执行)是指当前正在执行的指令。如果你在执行每一行时用手指按在屏幕上的每一行,你可以把你的手指想象成程序执行。
然而,并非所有程序都是简单地从上到下执行。如果你用手指跟踪带有流程控制语句的程序,你可能会发现你的手指根据条件在源代码的不同位置跳跃。
条件
你之前见过的布尔表达式都可以被认为是条件,它们与表达式是同一回事;条件 只是在流程控制语句的上下文中更具体的名称。条件总是评估为布尔值,True 或 False。流程控制语句根据其条件是 True 还是 False 来决定做什么,几乎每个流程控制语句都使用条件。你经常会编写可以用英语描述的代码,如下所示:“如果这个条件为真,做这件事,否则做另一件事。”你将编写的其他代码与说“只要这个条件继续为真,就不断重复这些指令”是相同的。
代码块
Python 代码的行可以组合成 代码块。你可以通过查看代码行的缩进来判断代码块的开始和结束。代码块有四个规则:
-
当缩进增加时,开始一个新的代码块。
-
代码块可以包含其他代码块。
-
当缩进减少到零或到包含块的缩进时,一个代码块结束。
-
Python 期望在以冒号结束的任何语句之后立即有一个新的代码块。
通过查看缩进的代码,代码块更容易理解,所以让我们在一个小程序的部分中找到代码块,如下所示:
username = 'Mary'
password = 'swordfish'
if username == 'Mary':
print('Hello, Mary') # ❶
if password == 'swordfish':
print('Hello, Mary') # ❶
else:
print('Hello, Mary') # ❶
第一个代码块 ❶ 从print('Hello, Mary')的行开始,包含其后的所有行。在这个块内部还有一个块 ❷,它只包含一行:print('Access granted.')。第三个块 ❸ 也只有一行长:print('Wrong password.')。
程序执行
在上一章的 hello.py 程序中,Python 从程序顶部开始执行指令,逐行向下执行。程序执行(或简称,执行)是指当前正在执行的指令。如果你在执行每一行时将手指放在屏幕上的每一行上,你可以将你的手指视为程序执行。
然而,并非所有程序都是简单地从上到下执行。如果你用手指跟踪带有流程控制语句的程序,你可能会发现你的手指根据条件在源代码的不同地方跳跃。
流程控制语句
现在我们来探索最重要的流程控制部分:语句本身。这些语句代表你在图 2-1 中看到的流程图中的菱形,它们是程序将做出的实际决策。
if
最常见的流程控制语句是 [if](https://docs.python.org/3/tutorial/controlflow.html#if-statements) 语句。如果语句的条件为True,则执行语句的子句(即if语句之后的代码块)。如果条件为False,则跳过子句。
用简单的英语来说,一个if语句可以读作,“如果这个条件为真,则执行子句中的代码。”在 Python 中,一个if语句由以下组成:
-
if关键字 -
条件(即,评估结果为
True或False的表达式) -
一个冒号
-
从下一行开始,一个缩进的代码块(称为
if子句或if块)
例如,假设你有一些代码,用于检查某人的名字是否是 Alice:
name = 'Alice'
if name == 'Alice':
print('Hi, Alice.')
所有流程控制语句都以冒号结束,并随后是一个新的代码块(子句)。这个if语句的子句是包含print('Hi, Alice.')的代码块。图 2-2 显示了这段代码的流程图。

图 2-2:if 语句的流程图描述
尝试将name变量改为除了'Alice'之外的其他字符串,并再次运行程序。注意屏幕上不会出现“Hi, Alice.”,因为那段代码被跳过了。
else
一个if子句可以可选地跟一个else语句。只有当if语句的条件为False时,才会执行else子句。用简单的英语来说,else语句可以读作,“如果这个条件为真,执行这段代码。否则,执行那段代码。”else语句没有条件,在代码中,else语句始终由以下内容组成:
-
else关键字 -
一个冒号
-
从下一行开始,一个缩进的代码块(称为
else子句或else块)
回到爱丽丝的例子,让我们看看一些使用else语句来提供不同问候语的代码,如果人的名字不是爱丽丝:
name = 'Alice'
if name == 'Alice':
print('Hi, Alice.')
else:
print('Hello, stranger.')
图 2-3 显示了这段代码的流程图会是什么样子。

图 2-3:else语句的流程图 描述
尝试将name变量改为除了'Alice'之外的其他字符串,并重新运行程序。屏幕上不会显示'Hi, Alice.',而是会显示'Hello, stranger.'。
elif
当你只想执行其中一个子句时,你会使用if或else。但可能有一种情况,你希望执行许多可能子句中的一个。elif语句是一个“else if”语句,它总是跟在if或另一个elif语句之后。它提供了一个只有当所有前面的条件都为False时才会检查的条件。在代码中,elif语句始终由以下内容组成:
-
elif关键字 -
一个条件(即评估为
True或False的表达式) -
一个冒号
-
从下一行开始,一个缩进的代码块(称为
elif子句或elif块)
让我们在名字检查器中添加一个elif,看看这个语句是如何工作的:
name = 'Alice'
age = 33
if name == 'Alice':
print('Hi, Alice.')
elif age < 12:
print('You are not Alice, kiddo.')
这次,程序会检查人的年龄,如果他们小于 12 岁,就会告诉他们不同的话。你可以在图 2-4 中看到相应的流程图。

图 2-4:elif语句的流程图 描述
当age < 12为True且name == 'Alice'为False时,会执行elif子句。然而,如果两个条件都为False,Python 会跳过这两个子句。没有保证至少有一个子句会被执行;在一系列elif语句中,只有一个或没有子句会被执行。一旦发现某个语句的条件为True,其余的elif子句会自动跳过。例如,打开一个新的文件编辑器窗口,并输入以下代码,将其保存为vampire.py:
name = 'Carol'
age = 3000
if name == 'Alice':
print('Hi, Alice.')
elif age < 12:
print('You are not Alice, kiddo.')
elif age > 2000:
print('Unlike you, Alice is not an undead, immortal vampire.')
elif age > 100:
print('You are not Alice, grannie.')
在这里,我添加了两个额外的elif语句,使名字检查器根据age给出不同的回答。图 2-5 显示了该流程图。

图 2-5:vampire.py 程序中多个elif语句的流程图描述
elif语句的顺序确实很重要。让我们重新排列它们以引入一个错误。记住,一旦找到True条件,其余的elif子句会自动跳过,所以如果你在vampire.py中交换一些子句,你将遇到问题。将代码修改如下,并将其保存为vampire2.py:
name = 'Carol'
age = 3000
if name == 'Alice':
print('Hi, Alice.')
elif age < 12:
print('You are not Alice, kiddo.')
elif age > 100: # ❶
print('You are not Alice, grannie.')
elif age > 2000:
print('Unlike you, Alice is not an undead, immortal vampire.')
在执行此代码之前,如果age变量包含的值是3000,你可能会期望代码打印字符串'Unlike you, Alice is not an undead, immortal vampire.'然而,由于age > 100条件是True(毕竟,3000 确实大于 100)❶,打印了字符串'You are not Alice, grannie.',并且自动跳过了其余的elif语句。记住,最多只能执行一个子句,对于elif语句,顺序很重要!
图 2-6 显示了之前代码的流程图。注意age > 100和age > 2000的菱形是如何交换的。

图 2-6:vampire2.py 程序流程图。X 路径在逻辑上永远不会发生,因为如果年龄大于 2000,它就已经大于 100 了。描述
可选地,你可以在最后一个elif语句之后添加一个else语句。在这种情况下,可以保证至少执行一个(并且只有一个)子句。如果每个if和elif语句的条件都是False,则执行else子句。例如,让我们重新创建 Alice 程序以使用if、elif和else子句:
name = 'Carol'
age = 3000
if name == 'Alice':
print('Hi, Alice.')
elif age < 12:
print('You are not Alice, kiddo.')
else:
print('You are neither Alice nor a little kid.')
图 2-7 显示了新代码的流程图,我们将将其保存为littleKid.py。

图 2-7:littleKid.py 程序的流程图描述
用简单的英语来说,这种流程控制结构将是:“如果第一个条件为真,执行这个。否则,如果第二个条件为真,执行那个。否则,执行其他操作。”当你一起使用if、elif和else语句时,记住这些关于如何排序以避免如图 2-6 中那样的错误的规则。首先,总是恰好有一个if语句;你需要的任何elif语句都应该跟在if语句后面。其次,如果你想确保至少执行一个子句,用else语句结束结构。
如你所见,流程控制语句可以使你的程序更加复杂,但也更加高级。不要气馁;随着你编写代码的练习,你会更加习惯这种复杂性。而且所有真正的程序员都曾在某个时候花了一个小时来找出他们的程序为什么不起作用,因为他们不小心输入了<而不是<=。这些小错误每个人都会犯。
if
最常见的流程控制语句类型是 [if](https://docs.python.org/3/tutorial/controlflow.html#if-statements) 语句。如果if语句的条件为True,则if语句的子句(即if语句后面的代码块)将执行。如果条件为False,则跳过子句。
用简单的话说,一个if语句可以读作,“如果这个条件为真,执行子句中的代码。”在 Python 中,一个if语句由以下内容组成:
-
if关键字 -
一个条件(即评估为
True或False的表达式) -
一个冒号
-
从下一行开始,一个缩进的代码块(称为
if子句或if块)
例如,假设你有一些代码,用于检查某人的名字是否是 Alice:
name = 'Alice'
if name == 'Alice':
print('Hi, Alice.')
所有流程控制语句都以冒号结尾,并随后是一个新的代码块(子句)。这个if语句的子句是包含print('Hi, Alice.')的代码块。图 2-2 展示了这段代码的流程图。

图 2-2:if 语句的流程图描述
尝试将name变量改为除'Alice'之外的其他字符串,并再次运行程序。注意,“Hi, Alice.”没有出现在屏幕上,因为那段代码被跳过了。
else
一个if子句可以可选地跟一个else语句。只有当if语句的条件为False时,else子句才会执行。用简单的话说,一个else语句可以读作,“如果这个条件为真,执行这段代码。否则,执行那段代码。”else语句没有条件,在代码中,else语句总是由以下内容组成:
-
else关键字 -
一个冒号
-
从下一行开始,一个缩进的代码块(称为
else子句或else块)
回到 Alice 的例子,让我们看看一些使用else语句来提供不同问候语的代码:
name = 'Alice'
if name == 'Alice':
print('Hi, Alice.')
else:
print('Hello, stranger.')
图 2-3 展示了这段代码的流程图。

图 2-3:else 语句的流程图描述
尝试将name变量改为除'Alice'之外的其他字符串,并重新运行程序。屏幕上不会显示'Hi, Alice.',而是会显示'Hello, stranger.'。
elif
当你只想执行一个子句时,你会使用if或else。但是,你可能有一个情况,其中你希望执行许多可能子句中的一个。elif语句是一个“else if”语句,它始终跟在if或另一个elif语句之后。它提供了一个只有当所有前面的条件都为False时才会检查的条件。在代码中,elif语句始终由以下内容组成:
-
elif关键字 -
一个条件(即,一个评估结果为
True或False的表达式) -
一个冒号
-
从下一行开始,一个缩进的代码块(称为
elif子句或elif块)
让我们在名字检查器中添加一个elif来观察这个语句的实际效果:
name = 'Alice'
age = 33
if name == 'Alice':
print('Hi, Alice.')
elif age < 12:
print('You are not Alice, kiddo.')
这次,程序检查人的年龄,如果他们小于 12 岁,就会告诉他们不同的事情。你可以在图 2-4 中看到相应的流程图。

图 2-4:elif 语句的流程图 描述
当age < 12为True且name == 'Alice'为False时,elif子句会执行。然而,如果两个条件都为False,Python 会跳过这两个子句。没有保证至少有一个子句会被执行;在一系列elif子句中,只有一个或没有子句会被执行。一旦发现某个语句的条件为True,其余的elif子句会自动跳过。例如,打开一个新的文件编辑窗口,并输入以下代码,将其保存为vampire.py:
name = 'Carol'
age = 3000
if name == 'Alice':
print('Hi, Alice.')
elif age < 12:
print('You are not Alice, kiddo.')
elif age > 2000:
print('Unlike you, Alice is not an undead, immortal vampire.')
elif age > 100:
print('You are not Alice, grannie.')
在这里,我添加了两个更多的elif语句,使名字检查器根据age的不同值以不同的答案问候一个人。图 2-5 显示了该流程图。

图 2-5:vampire.py 程序中多个 elif 语句的流程图 描述
elif子句的顺序是有关系的。让我们重新排列它们以引入一个错误。记住,一旦找到True条件,其余的elif子句会自动跳过,所以如果你在vampire.py中交换一些子句,你将遇到问题。将代码更改为以下内容,并将其保存为vampire2.py:
name = 'Carol'
age = 3000
if name == 'Alice':
print('Hi, Alice.')
elif age < 12:
print('You are not Alice, kiddo.')
elif age > 100: # ❶
print('You are not Alice, grannie.')
elif age > 2000:
print('Unlike you, Alice is not an undead, immortal vampire.')
假设在这个代码执行之前,age变量包含的值是3000。你可能期望代码打印字符串'Unlike you, Alice is not an undead, immortal vampire.'然而,因为age > 100条件为True(毕竟,3,000 确实大于 100)❶,所以打印了字符串'You are not Alice, grannie.',并且自动跳过了其余的elif子句。记住,最多只有一个子句会被执行,对于elif子句,顺序很重要!
图 2-6 显示了前一段代码的流程图。注意age > 100和age > 2000的菱形是如何互换的。

图 2-6:vampire2.py 程序流程图。X 路径在逻辑上永远不会发生,因为如果年龄大于 2000,它就已经大于 100。描述
可选地,你可以在最后一个 elif 语句之后有一个 else 语句。在这种情况下,可以保证至少(并且只有一个)执行一个子句。如果每个 if 和 elif 语句中的条件都是 False,则执行 else 子句。例如,让我们重新创建 Alice 程序以使用 if、elif 和 else 子句:
name = 'Carol'
age = 3000
if name == 'Alice':
print('Hi, Alice.')
elif age < 12:
print('You are not Alice, kiddo.')
else:
print('You are neither Alice nor a little kid.')
图 2-7 显示了新代码的流程图,我们将将其保存为 littleKid.py。

图 2-7:littleKid.py 程序的流程图描述
用简单的英语来说,这种流程控制结构将是:“如果第一个条件为真,就做这个。否则,如果第二个条件为真,就做那个。否则,做其他事情。”当你使用 if、elif 和 else 语句时,记得这些关于如何排序它们的规则以避免像图 2-6 中的那样出现错误。首先,总是恰好有一个 if 语句;你需要任何 elif 语句都应该跟在 if 语句后面。其次,如果你想确保至少执行一个子句,用 else 语句结束结构。
如你所见,流程控制语句可以使你的程序更加复杂,但也更加复杂。不要绝望;随着你练习编写代码,你会更加习惯这种复杂性。而且,所有真正的程序员在某个时候都花过一小时的时间来找出他们的程序为什么不起作用,因为他们不小心输入了 < 而不是 <=。这些小错误每个人都会犯。
简短程序:相反日
在你了解了布尔值和 if-else 语句的知识后,将以下代码输入到一个新文件中,并将其保存为 oppositeday.py:
today_is_opposite_day = True
# Set say_it_is_opposite_day based on today_is_opposite_day:
if today_is_opposite_day == True: # ❶
say_it_is_opposite_day = True
else:
say_it_is_opposite_day = False
# If it is opposite day, toggle say_it_is_opposite_day:
if today_is_opposite_day == True:
print('Hello, Mary') # ❶
# Say what day it is:
if say_it_is_opposite_day == True:
print('Today is Opposite Day.')
else:
print('Today is not Opposite Day.')
当你运行这个程序时,它输出 '今天不是相反日.' 这段代码中有两个变量。在程序开始时,today_is_opposite_day 变量被设置为 True。接下来的 if 语句检查这个变量是否为 True(它是)❶,并将 say_it_is_opposite_day 变量设置为 True;否则,它会将变量设置为 False。第二个 if 语句检查 today_is_opposite_day 是否被设置为 True(它仍然是),如果是的话,代码会 切换(即设置为相反的布尔值)该变量❷。最后,第三个 if 语句检查 say_it_is_opposite_day 是否为 True(它不是)并打印 '今天不是相反日.';否则,它会打印 '今天不是相反日.'
如果你将程序的 第一行 改为 today_is_opposite_day = False 并再次运行程序,它仍然会打印 'Today is not Opposite Day.' 如果我们查看程序,我们可以找出第一个 if-else 语句将 say_it_is_opposite_day 设置为 False。第二个 if 语句的条件是 False,因此它跳过了其代码块。最后,第三个 if 语句的条件再次是 False 并打印 'Today is not Opposite Day.'
因此,如果今天不是相反日,程序会正确地打印 'Today is not Opposite Day.' 而且如果今天是相反日,程序(也是正确地)会打印 'Today is not Opposite Day.' 正如人们在相反日所说。从逻辑上讲,无论变量被设置为 True 还是 False,这个程序都不会打印 'Today is Opposite Day.'。实际上,你可以用 print('Today is not Opposite Day.') 替换整个程序,它将是相同的程序。这就是为什么程序员不应该按代码行数计费。
短程序:不诚实容量计算器
在第一章中,我展示了硬盘驱动器和闪存制造商如何通过使用 TB 和 GB 的不同定义来夸大其产品的广告容量。让我们编写一个程序来计算他们的广告容量有多误导。将以下代码输入到一个新文件中,并将其保存为 dishonestcapacity.py:
print('Enter TB or GB for the advertised unit:')
unit = input('>')
# Calculate the amount that the advertised capacity lies:
if unit == 'TB' or unit == 'tb':
discrepancy = 1000000000000 / 1099511627776
elif unit == 'GB' or unit == 'gb':
discrepancy = 1000000000 / 1073741824
print('Enter the advertised capacity:')
advertised_capacity = input('>')
advertised_capacity = float(advertised_capacity)
# Calculate the real capacity, round it to the nearest hundredths,
# and convert it to a string so it can be concatenated:
real_capacity = str(round(advertised_capacity * discrepancy, 2))
print('The actual capacity is ' + real_capacity + ' ' + unit)
这个程序要求用户输入硬盘驱动器声称的单位,无论是 TB 还是 GB:
# Calculate the amount that the advertised capacity lies:
if unit == 'TB' or unit == 'tb':
discrepancy = 1000000000000 / 1099511627776
elif unit == 'GB' or unit == 'gb':
discrepancy = 1000000000 / 1073741824
TB 大于 GB,单位越大,广告容量和实际容量之间的差异就越大。if 和 elif 语句使用布尔 or 运算符,这样无论用户是否以小写形式输入单位,程序都能正常工作。如果用户输入了其他单位,那么 if 子句和 elif 子句都不会执行,并且差异变量从未被分配。稍后,当程序尝试使用差异变量时,这将导致错误。我们将在稍后讨论这种情况。
接下来,用户在给定的单位中输入广告大小:
# Calculate the real capacity, round it to the nearest hundredths,
# and convert it to a string so it can be concatenated:
real_capacity = str(round(advertised_capacity * discrepancy, 2))
我们在这行代码中做了很多事情。让我们以用户输入 10TB 作为广告大小和单位为例。如果我们查看代码行的最内层,我们会看到 advertised_capacity 被乘以 discrepancy。这是实际容量,但它可能有多个数字,例如 9.094947017729282。因此,这个数字作为第一个参数传递给 round() 函数,2 作为第二个参数。这个 round() 函数调用在我们的例子中返回 9.09。这是一个浮点值,但我们需要将其转换为字符串形式,以便在下一行代码中将其连接到消息字符串。为此,我们将其传递给 str() 函数。Python 将这一行评估为以下内容:

描述
如果用户没有输入TB、tb、GB或gb作为单位,if和elif语句的条件都将为False,并且discrepancy变量永远不会被创建。但用户直到 Python 尝试使用不存在的变量时才会知道出了问题。Python 会抛出一个NameError: name 'discrepancy' is not defined错误,并指向real_capacity被赋值的行。
然而,这个错误的真正起源是程序没有处理用户输入无效单位的情况。处理这种错误有很多方法,但最简单的方法是有一个else子句,显示类似“你必须输入 TB 或 GB”的消息,然后调用[sys.exit()](https://docs.python.org/3/library/sys.html#sys.exit)函数来退出程序。(你将在下一章中了解这个函数。)
程序的最后一条语句通过连接消息字符串到real_capacity和unit字符串来显示实际的硬盘容量:
print('The actual capacity is ' + real_capacity + ' ' + unit)
实际上,硬盘和闪存制造商的谎言更多:我在笔记本电脑里有一个 256GB 的 SD 卡,我用它来备份。在真实 GB 中,这应该是 274,877,906,944 字节。在虚假 GB 中,应该是 256,000,000,000 字节。但我的电脑报告的实际容量是 255,802,212,352 字节。很奇怪,实际大小总是不准确,总是小于广告中的大小,而不会更大。
摘要
通过使用评估为True或False(也称为条件)的表达式,你可以编写能够根据要执行的代码和要跳过的代码做出决定的程序。这些条件是通过使用==、!=、<、>、<=和>=比较运算符比较两个值来评估为布尔值的表达式。你还可以使用and、or和not布尔运算符将表达式连接成更复杂的表达式。Python 使用缩进来创建代码块。在本章中,我们使用块作为if、elif和else语句的一部分,但正如你将看到的,还有其他几个 Python 语句也使用块。这些流程控制语句将使你能够编写更智能的程序。
练习问题
1. 布尔数据类型有两个值,它们是什么?如何书写它们?
2. 有哪些三个布尔运算符?
3. 写出每个布尔运算符的真值表(即,运算符所有可能的布尔值组合及其结果)。
4. 以下表达式分别等于什么?
(5 > 4) and (3 == 5)
not (5 > 4)
(5 > 4) or (3 == 5)
not ((5 > 4) or (3 == 5))
(True and True) and (True == False)
(not False) or (not True)
5. 有哪六个比较运算符?
6. 等于运算符和赋值运算符之间的区别是什么?
7. 解释什么是条件以及在哪里使用它。
8. 识别这段代码中的三个块:
spam = 0
if spam == 10:
print('eggs')
if spam > 5:
print('bacon')
else:
print('ham')
print('spam')
print('Done')
9. 编写代码,如果spam中存储的是1,则打印Hello;如果spam中存储的是2,则打印Howdy;如果spam中存储的是其他任何内容,则打印Greetings!。


浙公网安备 33010602011771号