From 8c75ebd6cbcd21f98622ed4e650f1020a6fd7d85 Mon Sep 17 00:00:00 2001 From: clcs <143490477+clcs@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:51:01 +0800 Subject: [PATCH 01/11] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sicp/1/1.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/sicp/1/1.md b/sicp/1/1.md index eec23fd..d59810f 100644 --- a/sicp/1/1.md +++ b/sicp/1/1.md @@ -1,7 +1,7 @@ # 1.1 开始 ::: details INFO -译者:[Mancuoj](https://github.com/mancuoj) +译者:[mancuoj](https://github.com/mancuoj), [clcs](https://github.com/clcs) 来源:[1.1 Getting Started](https://www.composingprograms.com/pages/11-getting-started.html) @@ -24,7 +24,7 @@ **Python** 是一种应用广泛的编程语言,它吸引了来自许多行业的爱好者:Web 程序员、游戏工程师、科学家、学者,甚至新编程语言的设计者。当你学习 Python 时,你就加入了**一个拥有数百万开发者的强大社区**。开发者社区是非常重要的机构:成员们互相帮助解决问题、分享项目和经验,并共同开发软件和工具。做出贡献的忠实成员通常会获得声望和广泛的尊重。 -Python 语言本身是一个**庞大志愿者社区的产物**,它以其贡献者的 [多元化](http://python.org/community/diversity/) 为傲。该语言由 [Guido van Rossum](http://en.wikipedia.org/wiki/Guido_van_Rossum) 在 20 世纪 80 年代末构思并实现,他在  [Python 3 教程](http://docs.python.org/py3k/tutorial/appetite.html)  的第一章解释了在当今众多语言中,Python 为何如此受欢迎。 +Python 语言本身是一个**庞大志愿者社区的产物**,它以其贡献者的 [多元化](http://python.org/community/diversity/) 为傲。该语言由 [Guido van Rossum](http://en.wikipedia.org/wiki/Guido_van_Rossum) 在 20 世纪 80 年代末构思并实现,他在 [Python 3 教程](http://docs.python.org/py3k/tutorial/appetite.html) 的第一章解释了在当今众多语言中,Python 为何如此受欢迎。 Python 作为一种教学语言非常出色,因为在其整个历史中,Python 的开发人员一直在强调 Python 代码的**人类可读性**,并在 [Python 之禅](http://www.python.org/dev/peps/pep-0020/) 的**美观、简洁和可读性**的原则下进一步加强。因为它宽泛的特性能够支持各种不同的编程风格,所以十分适合本书,我们将在之后逐一探讨这些风格。从来没有单一的 Python 编程方法,但遵守开发人员社区共享的一组约定会有助于现有程序的阅读、理解和扩展。Python 巨大的灵活性和易学性可以使学生探索许多编程范式,然后将获得的新知识应用到数以千计的 [正在进行的项目](http://pypi.python.org/pypi) 中。 @@ -38,7 +38,7 @@ Python 作为一种教学语言非常出色,因为在其整个历史中,Pyth 你可以从 Python 下载页面点击以 3 开头的版本下载 Python 3,并按照安装程序的说明完成安装。 -如需进一步指导,请查看 Julia Oh 创建的有关 Python 3 的 [Windows 安装](http://www.youtube.com/watch?v=54-wuFsPi0w)  和 [Mac 安装](http://www.youtube.com/watch?v=smHuBHxJdK8) 的视频教程。 +如需进一步指导,请查看 Julia Oh 创建的有关 Python 3 的 [Windows 安装](http://www.youtube.com/watch?v=54-wuFsPi0w) 和 [Mac 安装](http://www.youtube.com/watch?v=smHuBHxJdK8) 的视频教程。 ## 1.1.3 交互式会话 @@ -67,9 +67,9 @@ Python 作为一种教学语言非常出色,因为在其整个历史中,Pyth > > — William Shakespeare, A Midsummer-Night's Dream -为了正式介绍 Python,我们将从一个使用多种语言功能的示例开始。在下一节中,我们将从头开始,逐步构建语言的各个部分。本节可视为对即将学习的功能的**预习**。 +为了正式介绍 Python,我们先从一个使用几个**语言特性**的例子开始。在下一节中,我们将从头开始,一点一点地构建这门语言。本节可以看作是未来特性的**抢先预览**。 -Python 支持广泛的常见编程功能,例如处理文本、显示图形以及通过互联网进行通信。下面这行 Python 代码: +Python 为广泛的常见编程活动提供了**内置支持**,例如处理文本、显示图形以及通过互联网进行通信。下面这行 Python 代码: ```py >>> from urllib.request import urlopen @@ -77,7 +77,7 @@ Python 支持广泛的常见编程功能,例如处理文本、显示图形以 是一个 `import` 语句,它会导入一个用于“访问互联网数据”的功能,该功能特别提供了一个名为 `urlopen` 的函数,可以访问统一资源定位符(URL,也就是互联网上的某个网址)上的内容。 -**语句和表达式**:Python 代码由表达式(expressions)和语句(statements)组成。广义上讲,计算机程序包含以下两种指令: +**语句和表达式**。Python 代码由表达式(expressions)和语句(statements)组成。广义上讲,计算机程序包含以下两种指令: 1. **计算某个值** 2. **执行某个操作** @@ -92,7 +92,7 @@ Python 支持广泛的常见编程功能,例如处理文本、显示图形以 将名称 `shakespeare` 与等号 `=` 后面的表达式的值关联起来。该表达式将 `urlopen` 函数应用在了一个包含莎士比亚 37 部戏剧完整文本的 URL 上。 -**函数**:函数封装了**操纵数据的逻辑**。`urlopen` 就是一个函数,一个网址是一段数据,莎士比亚的戏剧文本是另一段数据。从前者到后者的转换过程可能会很复杂,但我们可以**将这种复杂性隐藏在一个函数中**,从而能够使用一个简单的表达式来跳过该过程。**函数是本章的主题**。 +**函数**。函数封装了**操纵数据的逻辑**。`urlopen` 就是一个函数,一个网址是一段数据,莎士比亚的戏剧文本是另一段数据。从前者到后者的转换过程可能会很复杂,但我们可以**将这种复杂性隐藏在一个函数中**,从而能够使用一个简单的表达式来跳过该过程。**函数是本章的主题**。 另一个赋值语句: @@ -102,7 +102,7 @@ Python 支持广泛的常见编程功能,例如处理文本、显示图形以 将 `words` 与莎士比亚戏剧中出现的共 33,721 个单词的集合相连。`read`、`decode` 和 `split` 这一连串的命令,每个都作用于一个**中间计算实体**:从 URL 中**读取**数据,然后将数据**解码**为文本,最后将文本**拆分**为单词放在一个**集合**中。 -**对象**:**集合**(set)就是一种对象(object),它支持诸如计算交集和集合关系(membership)等集合操作。一个对象**无缝地将数据和操纵该数据的逻辑捆绑在一起**,以一种方式管理着两者带来的复杂性。对象是第二章的主题。 +**对象**。**集合**(set)就是一种对象(object),它支持诸如计算交集和集合关系(membership)等集合操作。一个对象**无缝地将数据和操纵该数据的逻辑捆绑在一起**,以一种方式管理着两者带来的复杂性。对象是第二章的主题。 最后,这个表达式: @@ -113,7 +113,7 @@ Python 支持广泛的常见编程功能,例如处理文本、显示图形以 是一个**复合表达式**,它求值得到所有莎士比亚戏剧中**反向拼写也是单词**的集合。神秘符号 `w[::-1]` 表示枚举一个单词中的每个字母,其中 -1 代表反向。当你在交互式会话中输入表达式时,Python 会在下一行**打印它的值**。 -**解释器**:复合表达式的求解需要以一个**可预测的方式来精确解释代码**的过程。实现这样的过程,用于**求解复合表达式**的程序就称为解释器。解释器的设计和实现是第三章的主题。 +**解释器**。复合表达式的求解需要以一个**可预测的方式来精确解释代码**的过程。实现这样的过程,用于**求解复合表达式**的程序就称为解释器。解释器的设计和实现是第三章的主题。 与其他计算机程序相比,编程语言的解释器以其**通用性**而独具一格。Python 在设计时并没有考虑莎士比亚。然而,它巨大的灵活性使我们能够仅用几条语句和表达式就处理了大量的文本。 @@ -121,7 +121,7 @@ Python 支持广泛的常见编程功能,例如处理文本、显示图形以 ## 1.1.5 错误 -Python 正在等待你的命令。即使你可能还不了解其完整的词汇和结构,我们仍鼓励你尝试使用该语言。当然也请你为错误做好准备,因为计算机在极其快速灵活的同时也十分古板。计算机的特性在  [斯坦福的入门课程](http://web.stanford.edu/class/cs101/code-1-introduction.html)  中被描述为 +Python 正在等待你的命令。即使你可能还不了解其完整的词汇和结构,我们仍鼓励你尝试使用该语言。当然也请你为错误做好准备,计算机在极其快速灵活的同时也十分死板。[斯坦福大学的入门课程](http://web.stanford.edu/class/cs101/code-1-introduction.html) 对**计算机的本质**做出了如下描述: > The fundamental equation of computers is: `computer = powerful + stupid` > @@ -142,4 +142,4 @@ Python 正在等待你的命令。即使你可能还不了解其完整的词汇 3. **检查你的假设**(Check your assumptions):解释器会完全按照你的指令执行——不多也不少。当某些代码的行为与程序员假设的行为不匹配时,它们的输出就是不符合预期的。**明确你的假设**,然后将调试的工作集中在**验证你的假设是否真的成立**上。 4. **咨询别人**(Consult others):你不是一个人!如果你不理解错误信息,请询问朋友、老师或搜索引擎(译者注:AI 解千愁)。如果你已经找出了一个错误,但不知道如何纠正,请别人帮你看一看。在小组解决问题的过程中,会分享很多有价值的编程知识。 -增量测试、模块化设计、明确的假设和团队合作是贯穿本书的主题,希望它们也将贯穿你的计算机科学职业生涯。 +**增量测试、模块化设计、精确假设和团队合作**是贯穿本书的主题,希望它们也将贯穿你的计算机科学职业生涯。 From 6042b49467667cc78b6de26f87e8f9aed227e143 Mon Sep 17 00:00:00 2001 From: clcs <143490477+clcs@users.noreply.github.com> Date: Wed, 10 Dec 2025 14:32:19 +0800 Subject: [PATCH 02/11] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sicp/1/1.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sicp/1/1.md b/sicp/1/1.md index d59810f..6711e97 100644 --- a/sicp/1/1.md +++ b/sicp/1/1.md @@ -1,18 +1,18 @@ # 1.1 开始 ::: details INFO -译者:[mancuoj](https://github.com/mancuoj), [clcs](https://github.com/clcs) +译者:[mancuoj](https://github.com/mancuoj)、[clcs](https://github.com/clcs) 来源:[1.1 Getting Started](https://www.composingprograms.com/pages/11-getting-started.html) 对应:HW 01 ::: -计算机科学是一个**极其广阔的学术领域**。全球分布式系统、人工智能、机器人技术、图形学、安全、科学计算、计算机架构以及数十个新兴子领域每年都在不断涌现新技术和新发现。计算机科学的飞速发展已渗透到人类生活的方方面面。商业、通信、科学、艺术、休闲和政治都已经被重新塑造为计算领域。 +计算机科学是一个**极其广阔的学术领域**。全球分布式系统、人工智能、机器人、图形学、安全、科学计算、计算机架构以及数十个新兴子领域每年都在不断涌现新技术和新发现。计算机科学的飞速发展已渗透到人类生活的方方面面。商业、通信、科学、艺术、休闲和政治都已经被重新塑造为计算领域。 计算机科学之所以能取得如此高的生产力,是因为它建立在**一套优雅而强大的基本思想**之上。所有的计算都始于**信息的表示**、**处理信息的逻辑规范**,以及**设计抽象**来管理这种逻辑的复杂性。掌握这些基本原理需要我们精确地理解**计算机是如何解释程序并执行计算过程**的。 -长期以来,这些基本思想都是通过 Harold Abelson 和 Gerald Jay Sussman 与 Julie Sussman 合著的经典教材《计算机程序的构造与解释》(Structure and Interpretation of Computer Programs, SICP)进行教授的。这本书大量借鉴了该教材,原作者已慷慨地授权在 Creative Commons 许可下进行改编和重用。 +长期以来,这些基本思想都是通过 Harold Abelson、Gerald Jay Sussman 与 Julie Sussman 合著的经典教材《计算机程序的构造与解释》(Structure and Interpretation of Computer Programs, SICP)进行教授的。这本书大量借鉴了该教材,原作者已慷慨地授权在 Creative Commons 许可下进行改编和重用。 ## 1.1.1 Python 编程 From 61a3f940adade15ea1005beebd433c292d71ac7f Mon Sep 17 00:00:00 2001 From: clcs <143490477+clcs@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:07:17 +0800 Subject: [PATCH 03/11] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=88=B0=201.2.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sicp/1/2.md | 74 +++++++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/sicp/1/2.md b/sicp/1/2.md index d094f1c..f33b02e 100644 --- a/sicp/1/2.md +++ b/sicp/1/2.md @@ -1,35 +1,35 @@ # 1.2 编程要素 ::: details INFO -译者:[Mancuoj](https://github.com/mancuoj) +译者:[mancuoj](https://github.com/mancuoj)、[clcs](https://github.com/clcs) 来源:[1.2 Elements of Programming](https://www.composingprograms.com/pages/12-elements-of-programming.html) 对应:HW 01 ::: -编程语言不仅是一种指挥计算机执行任务的手段,它还应该成为一种框架,使我们能够在其中组织自己有关计算过程的思想。程序也用于在编程社区的成员之间传达想法,所以程序必须是人类可以阅读的,并且“恰巧”能被机器执行。 +编程语言不仅仅是指导计算机执行任务的工具,它还应该充当一个框架,使我们能够在其中组织自己有关计算过程的思想。程序也用于在编程社区的成员之间传达想法,因此,程序必须首先为人类阅读而编写,其次才是为机器执行而编写。 -这样,当我们描述一种语言时,就需要特别注意该语言所提供的能够将简单思想组合成复杂思想的工具。每一种强大的语言都有这样三种机制: +这样,当我们描述一种语言时,应特别关注该语言所提供的能够将简单想法组合成复杂想法的机制。每一种强大的语言都有这样三种机制: -- **原始表达式和语句**:语言所关心的最简单的个体 -- **组合方法**:由简单元素组合构建复合元素 -- **抽象方法**:命名复合元素,并将其作为单元进行操作 +- **原始表达式和语句**(primitive expressions and statements):语言提供的最简单的构建模块 +- **组合方法**(means of combination):由简单元素组合构建复合元素 +- **抽象方法**(means of abstraction):命名复合元素,并将其作为单元进行操作 -在编程中,我们只会处理两种元素:函数和数据(之后你会发现它们实际上并不是泾渭分明的),不那么正式的说法是:数据是我们想要操作的东西,而函数是操作这些数据的规则的描述。因此,任何强大的编程语言都必须能表达基本的数据和函数,并且提供对函数和数据进行组合和抽象的方法。 +在编程中,我们只会处理两种元素:**函数**和**数据**(很快你会发现它们实际上并不是泾渭分明的),通俗地说:数据是我们想要操作的材料,而函数描述了操作这些数据的“规则”。因此,任何强大的编程语言都必须能表达基本的数据和函数,并拥有组合和抽象函数与数据的方法。 ## 1.2.1 表达式 -上一节中,我们完整尝试了 Python 解释器,而下面我们将重新开始,一步步地讲解 Python 语言。如果示例看起来过于简单,请耐心等待,更令人振奋的东西在后面呢。 +上一节中,我们完整尝试了 Python 解释器,而下面我们将重新开始,一步步地讲解 Python 语言。如果示例看起来过于简单,请保持耐心,更精彩的内容很快就会到来。 -我们从一种基本表达式“数字 number”开始,更准确地说,是你键入的,十进制数字组成的表达式。 +我们从**基本表达式**开始。其中一种基本表达式是**数字**。更准确地说,您键入的表达式由代表该数字的十进制数字组成。 ```py >>> 42 42 ``` -表达式表示的数字可以与数学运算符组合形成一个复合表达式,解释器将对其进行求值: +表示数字的表达式可以与数学运算符结合,形成**复合表达式**,解释器将对其进行求值: ```py >>> -1 - -1 @@ -38,24 +38,24 @@ 0.9921875 ``` -这些数学表达式使用中缀表示法(infix notation),运算符(例如 +、-、\* 或 /)出现在操作数之间。Python 包含许多种形成复合表达式的方法,我们会在学习中慢慢引入新的表达式形式和它们所支持的语言特性,而不是立即把它们列举出来。 +这些数学表达式使用**中缀表示法**(infix notation),**运算符**(例如 `+`、`-`、`*` 或 `/`)出现在**操作数**之间。Python 包含许多种形成复合表达式的方法,我们会在学习中慢慢引入新的表达式形式和它们所支持的语言特性,而不是立即把它们列举出来。 ## 1.2.2 调用表达式 -最重要的一种复合表达式是调用表达式,它将函数运用于一些参数上。回想一下代数中的函数的数学概念:函数就是从一些输入参数到输出值的映射。例如,`max` 函数会输出一个最大的输入值,也就是将多个输入映射到了单个输出上。Python 中函数应用的方式与传统数学相同。 +最重要的一种复合表达式是**调用表达式**(call expression),它**将函数应用于某些参数**上。回想一下代数中的概念,函数的数学概念是**将某些输入参数映射到一个输出值**的过程。例如,`max` 函数会输出一个最大的输入值,也就是将多个输入映射到了一个输出。Python 中函数应用的方式与传统数学相同。 ```py >>> max(7.5, 9.5) 9.5 ``` -这个调用表达式包含子表达式(subexpressions):在括号之前是一个运算符表达式,而括号里面是一个以逗号分隔的操作数表达式的列表。 +这个调用表达式包含**子表达式**(subexpressions):运算符(operator)是一个表达式,它位于括号之前,而括号里面是一个以逗号分隔的操作数表达式(operand expressions)的列表。 ![call_expression](/sicp/call_expression.png) -运算符指定了一个函数,在对这个调用表达式进行求值时,我们会说:使用参数 7.5 和 9.5 来调用函数 `max`,最后返回 9.5。 +运算符指定了一个函数,在对这个调用表达式被求值时,我们会说:使用参数 `7.5` 和 `9.5` 来调用函数 `max`,最后返回一个值 `9.5`。 -调用表达式中参数的顺序是很重要的。例如,`pow` 函数的第二个参数是第一个参数的幂。 +参数在调用表达式中的顺序很重要。例如,`pow` 函数将第一个参数提高到其第二个参数的幂: ```py >>> pow(100, 2) @@ -64,27 +64,29 @@ 1267650600228229401496703205376 ``` -函数符号相比传统的中缀数学符号有三个主要优点。首先,因为函数名总是在参数前面,函数可以接收任意数量的参数而不会产生歧义。 +函数符号相比传统的中缀数学符号有三个主要优点。首先,**函数可以接受任意数量的参数**: ```py >>> max(1, -2, 3, -4) 3 ``` -其次,函数可以直接扩展为嵌套(nested)表达式,其元素本身就是复合表达式。不同于中缀复合表达式,调用表达式的嵌套结构在括号中是完全明确的。 +不会产生歧义,因为函数名称始终位于其参数之前。 + +其次,函数可以直接扩展为**嵌套表达式**(nested expressions),其元素本身就是复合表达式。不同于中缀复合表达式,调用表达式的**嵌套结构在括号中是完全明确的**。 ```py >>> max(min(1, -2), min(pow(3, 5), -4)) -2 ``` -这种嵌套的深度(理论上)没有任何限制,Python 解释器可以解释任何复杂的表达式。但人类很快就会被多层嵌套搞晕,所以作为一个程序员,你的一个重要目标就是:构造你自己、你的编程伙伴和其他任何可能阅读你代码的人都可以解释的表达式。 +这种嵌套的深度和表达式的整体复杂性(理论上)没有任何限制,Python 解释器可以解释任何复杂的表达式。然而,人类很快就会被多级嵌套搞糊涂。作为一名程序员,一个重要的角色是**组织表达式的结构**,以便你自己、你的编程伙伴以及未来可能阅读你表达式的其他人都能理解。 -第三点,数学符号在形式上多种多样:星号表示乘法,上标表示幂指数,水平横杠表示除法,带有倾斜壁板的屋顶表示平方根,而其中一些符号很难被输入!但是,所有这些复杂事物都可以通过调用表达式的符号来进行统一。Python 除了支持常见的中缀数学符号(如 + 和 -)之外,其他任何运算符都可以表示为一个带有名称的函数。 +第三点,统一了数学符号:星号表示乘法,上标表示指数,水平横杠表示除法,“带有倾斜壁板的屋顶”表示平方根,而其中一些符号很难输入!但是,所有这些复杂事物都可以通过调用表达式的符号来进行统一。虽然 Python 支持使用中缀表示法的常见数学运算符(如 `+` 和 `-`)之外,但**任何运算符都可以表示为一个带有名称的函数**。 ## 1.2.3 导入库函数 -Python 定义了大量的函数,包括上一节中提到的运算符函数,但默认情况下我们不能直接使用名字来调用它们。Python 将已知函数和其他东西组织起来放入到了模块中,而这些模块共同组成了 Python 库。我们要使用的时候需要导入它们,例如,`math`  模块提供了各种熟悉的数学函数: +Python 定义了大量的函数,包括上一节中提到的运算符函数,但默认情况下我们不能直接使用名字来调用它们。Python 将已知函数和其他东西组织起来放入到了**模块**(module)中,而这些模块共同组成了 Python 库。我们要使用的时候需要**导入**(import)它们,例如,`math` 模块提供了各种熟悉的数学函数: ```py >>> from math import sqrt @@ -92,7 +94,7 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数, 16.0 ``` -`operator`  模块提供了中缀运算符对应的函数: +而 `operator` 模块提供了中缀运算符对应的函数: ```py >>> from operator import add, sub, mul @@ -102,17 +104,17 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数, 16 ``` -`import`  语句需要指定模块名称(例如 `operator`  或  `math`),然后列出要导入该模块里的具名函数(例如 `sqrt`)。一个函数被导入后就可以被多次调用。 +`import` 语句需要指定模块名称(例如 `operator` 或 `math`),然后列出要导入该模块里的**命名属性**(例如 `sqrt`)。一旦函数被导入,就可以多次调用。 -使用运算符函数(例如 `add`)和运算符号(例如  +)之间并没有任何区别。按照惯例来说,大多数程序员使用符号和中缀表示法来表达简单的算术。 +使用运算符函数(例如 `add`)与使用运算符号本身(例如 `+`)之间并**没有本质区别**。按照惯例来说,大多数程序员使用符号和中缀表示法来表达简单的算术运算。 -[Python 3 库文档](http://docs.python.org/py3k/library/index.html)  列出了每个模块中定义的函数,例如 [math 模块](http://docs.python.org/py3k/library/math.html)。但是,该文档是为熟悉整个语言的开发人员编写的。现在来说,尝试使用函数可能会比阅读文档更能使你了解函数的行为。而当你熟悉了 Python 语言和词汇时,这个文档就将会成为你宝贵的参考资料。 +[Python 3 库文档](http://docs.python.org/py3k/library/index.html) 列出了每个模块中定义的函数,例如 [math 模块](http://docs.python.org/py3k/library/math.html)。但是,该文档是为熟悉整个语言的开发人员编写的。现在来说,**对函数进行实验比阅读文档更能了解其行为**。而当你熟悉了 Python 语言和词汇时,这个文档就将会成为你宝贵的参考资料。 ## 1.2.4 名称与环境 -编程语言的一个要素就是使用名称来引用计算对象,如果一个值被赋予了名称,我们说名称绑定到了值上面。 +编程语言的一个要素就是它提供了**使用名称来指代计算对象**的方法,如果一个值被赋予了名称,我们说名称**绑定**(binds)到了值上。 -在 Python 中,我们可以使用赋值语句建立新的绑定,`=` 左边是名称,右边是值。 +在 Python 中,我们可以使用赋值语句来建立新的绑定,`=` 左边是名称,右边是值: ```py >>> radius = 10 @@ -122,7 +124,7 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数, 20 ``` -名称也可以通过 `import` 语句绑定。 +名称也可以通过 `import` 语句绑定: ```py >>> from math import pi @@ -130,9 +132,9 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数, 1.0002380197528042 ``` -`=` 在 Python 中称为 **赋值** 符号(即 assignment operator,许多其他语言也是如此),赋值是最简单的 **抽象** 方法,因为它允许我们使用简单名称来指代复合操作的结果,例如上面计算的 `area`。这样,复杂的程序由复杂性递增的计算对象逐步构建。 +等号 `=` 在 Python(和许多其他语言)中被称为**赋值运算符**(assignment operator)。赋值是最简单的**抽象方法**,因为它允许我们使用简单的名称来指代复合操作的结果,例如上面计算的 `area`。这样,复杂的程序可以通过一步一步构建复杂度不断增加的计算对象来构造。 -将名称与值绑定,之后通过名称检索可能的值,就意味着解释器必须维护某种内存来记录名称、值和绑定,这种内存就是环境(environment)。 +将名称与值绑定,之后通过名称检索可能的值,意味着解释器必须维护某种**内存**来跟踪记录名称、值和绑定关系,这种内存被称为**环境**(environment)。 名称也可以与函数绑定。例如,名称 `max` 就和我们之前使用的 `max` 函数进行了绑定。与数字不同,函数很难以文本呈现,因此当询问一个函数时,Python 会打印一个标识来描述: @@ -141,7 +143,7 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数, ``` -赋值语句可以为现有函数赋予新名称。 +我们可以使用赋值语句为现有函数赋予新的名称: ```py >>> f = max @@ -151,7 +153,7 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数, 4 ``` -之后再次赋值可以将已有名称与新值绑定。 +并且连续的赋值语句可以将一个名称重新绑定到一个新的值: ```py >>> f = 2 @@ -159,7 +161,7 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数, 2 ``` -在 Python 中,名称通常被称为“变量名 variable names”或“变量 variables”,因为它们可以在执行程序的过程中与不同的值绑定。当一个变量通过赋值语句与一个新值绑定,它就不再绑定以前的值。你甚至可以将内置名称与新值绑定。 +在 Python 中,名称通常被称为**变量名**(variable names)或**变量**(variables),因为它们可以在程序执行过程中绑定到不同的值。当一个名称通过赋值绑定到一个新值时,它就不再绑定到任何旧值,甚至可以将内置名称绑定到新值: ```py >>> max = 5 @@ -169,7 +171,7 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数, 将 `max` 赋值为 5 后,名称 `max` 不再绑定函数,因此调用 `max(2, 3, 4)` 将导致错误。 -执行赋值语句时,Python 会先求解 `=` 右侧的表达式,再将结果与左侧的名称绑定,所以可以在右侧表达式中引用一个已绑定的变量。 +执行赋值语句时,Python 会**先求解** `=` 右侧的表达式,然后**再更改**左侧名称的绑定。因此,可以在右侧表达式中引用一个名称,即使它是赋值语句要绑定的名称: ```py >>> x = 2 @@ -178,7 +180,7 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数, 3 ``` -还可以在单个语句中为多个变量分配值,左右都用逗号隔开。 +我们还可以在一个语句中为多个名称赋多个值,其中 = 左侧的名称和 = 右侧的表达式都用逗号分隔: ```py >>> area, circumference = pi * radius * radius, 2 * pi * radius @@ -188,7 +190,7 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数, 62.83185307179586 ``` -更改一个变量的值不会影响其他变量。即使下列代码中 `area` 的值由最初定义的 `radius` 绑定,但改变 `radius` 的值并不能更新 `area` 的值,我们需要另一个赋值语句来更新它。 +改变一个名称的值**不会影响**其他名称。在下面,尽管名称 `area` 绑定到的值最初是根据 `radius` 定义的,但 `area` 的值并没有改变。更新 `area` 的值需要另一个赋值语句。 ```py >>> radius = 11 @@ -198,7 +200,7 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数, 380.132711084365 ``` -对于多重赋值,所有 `=` 右边的表达式都会先求值,然后再与左边的名称绑定。在这个规则下,我们可以在单个语句内交换两个变量的值。 +对于多重赋值,**所有** `=` 右边的表达式都会**先求值**,然后再与左边的名称绑定。在这个规则下,可以在一个语句中完成交换绑定到两个名称的值的操作。 ```py >>> x, y = 3, 4.5 From 94e18197e82f0e0c563edc60d299ef2b7223216c Mon Sep 17 00:00:00 2001 From: clcs <143490477+clcs@users.noreply.github.com> Date: Thu, 11 Dec 2025 10:48:42 +0800 Subject: [PATCH 04/11] update 1.2 --- sicp/1/2.md | 66 +++++++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/sicp/1/2.md b/sicp/1/2.md index f33b02e..1bf9b61 100644 --- a/sicp/1/2.md +++ b/sicp/1/2.md @@ -86,7 +86,7 @@ ## 1.2.3 导入库函数 -Python 定义了大量的函数,包括上一节中提到的运算符函数,但默认情况下我们不能直接使用名字来调用它们。Python 将已知函数和其他东西组织起来放入到了**模块**(module)中,而这些模块共同组成了 Python 库。我们要使用的时候需要**导入**(import)它们,例如,`math` 模块提供了各种熟悉的数学函数: +Python 定义了大量的函数,包括上一节中提到的运算符函数,但默认情况下我们不能直接使用名字来调用它们。Python 将已知函数和其他东西组织起来放入到了**模块**(module)中,而这些模块共同组成了 Python 标准库。我们要使用的时候需要**导入**(import)它们,例如,`math` 模块提供了各种熟悉的数学函数: ```py >>> from math import sqrt @@ -213,80 +213,82 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数, ## 1.2.5 求解嵌套表达式 -本章的目标之一是在“以程序的角度思考”中隔离其他的问题,举一个恰当的例子,就是思考一下在求解嵌套表达式时,解释器自身的操作过程。 +本章的一个重要目标,就是把**程序化思维**(procedural thinking)这个问题单独拎出来讨论。举例来说,在求值嵌套的调用表达式时,解释器本身其实也在遵循一个固定的过程。 -为了求值一个表达式,Python 将执行以下操作: +Python 求值一个调用表达式时,会按以下步骤进行: -- 求解运算符子表达式和操作数子表达式 -- 然后将操作数子表达式的值作为运算符子表达式的函数的参数 +1. 先分别求值运算符和操作数子表达式,得到它们的值 +2. 再把运算符子表达式的值(也就是函数)应用到操作数子表达式的值(也就是参数)上 -这个简单的过程也说明了有关流程的一些要点。第一步规定:为了求出调用表达式,必须首先求出其他表达式。因此,求值程序本质上是 **递归** 的,也就是说它会自己调用自己作为步骤之一。 +哪怕是这么简单的规则,也揭示了关于“计算过程”的一些本质特征。首先,第 1 步告诉我们发现:要想完成对一个调用表达式的求值,必须先去求值其他表达式。因此,这个求值过程本质上是**递归**的 —— 它的其中一个步骤,就是再次调用这个求值规则本身。 -例如,此式需要应用四次求值过程。 +举例来说,下面这行代码: ```py >>> sub(pow(2, add(1, 10)), pow(2, 5)) 2016 ``` -如果把每个需要求解的表达式都抽出来,我们可以看到这个求值过程的层次结构。 +要得到结果,整个求值规则一共被调用了四次。如果我们把每一次要求值的表达式都画出来,就能得到一个层次分明的结构: ![expression_tree](/sicp/expression_tree.png) -这个图叫做表达式树,在计算机科学中,树通常从上到下增长。树中每个点的对象都叫做节点。这里节点分别是表达式和表达式的值。 +这个图叫做**表达式树**(expression tree),在计算机科学里,树的生长方向通常是从上往下长的。树上的每一个点都叫作**节点**(nodes),这里每个节点都是一对“表达式+它的值”。 -求解根节点(即顶部的完整表达式),需要首先求解子表达式,也就是分支节点。叶子节点(也就是没有分支的节点)表示函数或数值。内部节点有两部分:我们想要应用的求值规则的调用表达式,以及该表达式的结果。观察这棵树的求解过程,我们可以想象操作数的值会向上流动,从叶子节点开始一步步向上组合。 +求**根节点**(root,即顶部的完整表达式)的值,必须先求它的**各分支**(branches,子表达式)的值。那些没有分支的**叶子节点**(leaf)要么是函数,要么是数值。而中间的节点则包含两部分:我们要应用求值规则的调用表达式,以及应用规则后得到的结果。从这棵树的角度来看,可以想象数值从最底层的叶子节点开始,一层一层往上传递、组合,最终汇聚到根节点。 -接下来,观察第一步的重复应用,将我们带到我们需要求解的原始表达式,而不是调用表达式,例如数字(例如 2)和名称(例如  add)。我们规定基本逻辑为: +接下来要注意的是:反复执行第 1 步,我们最终会碰到不再是调用表达式,而是最基本的**原始表达式**,例如数字(例如 `2`)和名称(例如 `add`)。我们通过下面这两条约定来处理这些原始情况: -- 数字的值就是它们所表示的数值 -- 名称的值是环境中关联此名称的对象 +- 数值就是数字的值(A numeral evaluates to the number it names) +- 名称取它在**当前环境**中绑定的值 -注意环境在决定表达式中的符号意义上有重要作用。在 Python 中,不指定任何环境信息去谈论一个值是没有意义的,例如名称 `x` 和 `add`。环境为求解提供了上下文信息,对理解程序执行过程有着重要作用。 +环境在这里起到了至关重要的作用。如果不给出环境信息,很多表达式的值根本无从谈起。例如: ```py >>> add(x, 1) ``` -这个求解步骤并不能对所有 Python 代码求值,它仅能求解调用表达式、数字和名称。例如,它并不能处理赋值语句。 +不说明 `x`(甚至 `add`)在当前环境里绑定的是什么,就根本无法确定这个表达式的值。环境为求值提供了**上下文**(context),这正是我们理解程序执行的关键。 + +上面给出的求值过程,目前只能处理调用表达式、数值和名称这三种情况,还不足以求值所有 Python 代码。比如它处理不了赋值语句: ```py >>> x = 3 ``` -因为赋值语句的目的是将名称与值绑定,它并不返回值,也不应用参数去求解函数。也就是说,赋值语句不被求解但“被执行”,它们只是做出一些改变但不产生值。每种类型的表达式或语句都有自己的求解或执行过程。 +它不会返回值,也不会把某个函数应用到参数上,它的目的只是把一个名称绑定到一个值上。一般来说,语句不是被**求值**(evaluated),而是被**执行**(executed);它们不产生值,而是**改变某些状态**。每一种表达式或语句都有自己对应的求值/执行规则。 -注意:当我们说“一个数字求解为一个数值”时,实际上是 Python 解释器将数字求解为数值,是解释器赋予了编程语言这个意义。鉴于解释器是一个始终表现一致的固定程序,我们就可以说数字(以及表达式)会在 Python 程序的上下文中被求解为值。 +注意:当我们说“数值就是数字的值”时,其实是指 Python 解释器把这个数字求解成了相应的数值。真正赋予程序语言意义的,是解释器自己。鉴于解释器是一个行为始终一致的固定程序,我们才可以说“在 Python 程序的上下文下,这个数字(和表达式)本身求值为某个值”。 ## 1.2.6 非纯函数 print -在本节中,我们将区分两种类型的函数。 +在本书中,我们将区分两种类型的函数。 -**纯函数(Pure functions)**:函数有一些输入(参数)并返回一些输出(调用返回结果)。 +**纯函数**(Pure functions):只有输入(参数),只有输出(返回值)。比如内置函数 `abs`: ```py >>> abs(-2) 2 ``` -可以将内置函数 `abs` 描述为接受输入并产生输出的小型机器。 +可以把它想象成一台小机器:丢进去一个数,吐出来另一个数。 ![function_abs](/sicp/function_abs.png) -`abs`  就是纯函数,纯函数在调用时除了返回值外不会造成其他任何影响,而且在使用相同的参数调用纯函数时总是会返回相同的值。 +纯函数的特性是:除了返回值之外没有别的**副作用**,而且**同样的参数永远返回完全相同的结果**。 -**非纯函数(Non-pure functions)**:除了返回值外,调用一个非纯函数还会产生其他改变解释器和计算机的状态的副作用(side effect)。一个常见的副作用就是使用 `print` 函数产生(非返回值的)额外输出。 +**非纯函数**(Non-pure functions):除了返回值之外,调用它们还会产生**副作用**(side effect),也就是改变解释器或计算机的某种状态。最常见的副作用就是用 `print` 在屏幕上额外输出内容: ```py >>> print(1, 2, 3) 1 2 3 ``` -虽然  `print`  和  `abs`  在这些例子中看起来很相似,但它们的工作方式基本不同。`print` 返回的值始终为 `None`,这是一个不代表任何内容的特殊 Python 值。而交互式 Python 解释器并不会自动打印 `None` 值,所以 `print` 函数的额外输出就是它的副作用。 +虽然 `print` 和 `abs` 在交互式环境里看起来差不多,但它们的工作方式完全不同。`print` 返回的值永远是 `None` —— Python 里代表“什么都没有”的特殊值。交互式解释器不会主动打印 `None` 值,所以上面例子中看到的 `1 2 3`,其实是 `print` 在执行过程中主动打印出来的**副作用**,而不是它的返回值。 ![function_print](/sicp/function_print.png) -下面这个调用 `print` 的嵌套表达式就展示了非纯函数的特征。 +一个嵌套的 `print` 调用能更清楚地展示它的**非纯特性**: ```py >>> print(print(1), print(2)) @@ -295,9 +297,9 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数, None None ``` -如果你发现这个输出结果出乎你的意料,可以画一个表达式树来解释求解该表达式会产生特殊输出的原因。 +如果你觉得这行输出很奇怪,建议画出表达式树,你立刻就能明白为什么会先打印 `1` 和 `2`,再打印两个 `None`。 -小心使用  `print` 函数!它返回 `None`  意味着它不应该用于赋值语句。 +一定要小心 `print`!因为它返回 `None`,所以绝不能把它直接用在赋值语句的右边: ```py >>> two = print(2) @@ -306,12 +308,12 @@ None None None ``` -纯函数不能有副作用,或是随着时间推移的改变的限制,但是对其施加这些限制会产生巨大的好处。首先,纯函数可以更可靠地组成复合调用表达式。在上面的示例中可以看到在操作数表达式中使用非纯函数  `print`  并不能返回有用的结果,但另一方面,我们已经看到  `max, pow, sqrt`  等函数可以在嵌套表达式中有效使用。 - -第二,纯函数往往更易于测试。相同的参数列表会返回相同的值,我们可以将其与预期的返回值进行比较。本章后面将更详细地讨论测试。 +纯函数受到严格限制:不能有副作用、不能随时间改变行为,但这恰恰带来了巨大好处: -第三,第四章将说明纯函数对于编写可以同时计算多个调用表达式的并发程序来说是必不可少的。 +1. 纯函数可以**非常可靠地嵌套组合**成更复杂的表达式。前面 `print` 的例子已经说明,非纯函数在子表达式位置往往会出问题;而 `max、pow、round` 这类纯函数却可以随意嵌套 +2. 纯函数**极易测试**:同样的参数永远得到同样的返回值,直接和预期结果对比即可。 +3. 第 4 章会讲到,编写**并发**程序时(多个表达式可能同时求值),纯函数几乎是唯一安全的选择。 -此外,第二章会研究一系列非纯函数并描述它们的用途。 +相比之下,第 2 章会介绍一大堆非纯函数以及它们的典型用途。 -所以我们将在本章的剩余部分重点介绍纯函数的创建和使用,`print` 函数仅用于查看计算的中间结果。 +正因为如此,本章余下内容将主要围绕**如何定义和使用纯函数**展开。`print` 只在需要看到中间计算结果时才会被用到。 From fe1d2be1087c3a63bdfb5225d2ae4b8d7926a11c Mon Sep 17 00:00:00 2001 From: clcs <143490477+clcs@users.noreply.github.com> Date: Thu, 11 Dec 2025 10:53:13 +0800 Subject: [PATCH 05/11] Update 2.md --- sicp/1/2.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sicp/1/2.md b/sicp/1/2.md index 1bf9b61..f65596b 100644 --- a/sicp/1/2.md +++ b/sicp/1/2.md @@ -64,7 +64,7 @@ 1267650600228229401496703205376 ``` -函数符号相比传统的中缀数学符号有三个主要优点。首先,**函数可以接受任意数量的参数**: +函数表示法相比传统的中缀表示法有三个主要优点。首先,**函数可以接受任意数量的参数**: ```py >>> max(1, -2, 3, -4) @@ -284,7 +284,7 @@ Python 求值一个调用表达式时,会按以下步骤进行: 1 2 3 ``` -虽然 `print` 和 `abs` 在交互式环境里看起来差不多,但它们的工作方式完全不同。`print` 返回的值永远是 `None` —— Python 里代表“什么都没有”的特殊值。交互式解释器不会主动打印 `None` 值,所以上面例子中看到的 `1 2 3`,其实是 `print` 在执行过程中主动打印出来的**副作用**,而不是它的返回值。 +虽然 `print` 和 `abs` 在交互式环境里看起来差不多,但它们的工作方式完全不同。`print` 返回的值永远是 `None` —— Python 里**代表“空”的特殊值**。交互式解释器不会主动打印 `None` 值,所以上面例子中看到的 `1 2 3`,其实是 `print` 在执行过程中主动打印出来的**副作用**,而不是它的返回值。 ![function_print](/sicp/function_print.png) From d9819433c16de99e88d7d2a8fd6db7d508b452af Mon Sep 17 00:00:00 2001 From: clcs <143490477+clcs@users.noreply.github.com> Date: Thu, 11 Dec 2025 10:58:00 +0800 Subject: [PATCH 06/11] Update 1.md --- sicp/1/1.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sicp/1/1.md b/sicp/1/1.md index 6711e97..2918902 100644 --- a/sicp/1/1.md +++ b/sicp/1/1.md @@ -87,7 +87,7 @@ Python 为广泛的常见编程活动提供了**内置支持**,例如处理文 这条**赋值语句**(assignment statement): ```py ->>> shakespeare = urlopen('https://www.composingprograms.com/shakespeare.txt') +>>> shakespeare = urlopen('http://composingprograms.com/shakespeare.txt') ``` 将名称 `shakespeare` 与等号 `=` 后面的表达式的值关联起来。该表达式将 `urlopen` 函数应用在了一个包含莎士比亚 37 部戏剧完整文本的 URL 上。 @@ -115,7 +115,7 @@ Python 为广泛的常见编程活动提供了**内置支持**,例如处理文 **解释器**。复合表达式的求解需要以一个**可预测的方式来精确解释代码**的过程。实现这样的过程,用于**求解复合表达式**的程序就称为解释器。解释器的设计和实现是第三章的主题。 -与其他计算机程序相比,编程语言的解释器以其**通用性**而独具一格。Python 在设计时并没有考虑莎士比亚。然而,它巨大的灵活性使我们能够仅用几条语句和表达式就处理了大量的文本。 +与其他计算机程序相比,编程语言的解释器以其**通用性**而独具一格。Python 设计之初可没想着要去处理莎士比亚全集,然而,它巨大的灵活性使我们能够仅用几条语句和表达式就处理了大量的文本。 最后,我们会发现所有这些核心概念都是紧密相关的:**函数是对象,对象是函数,解释器是二者的实例**。但是,清楚地理解每一个概念及其在组织代码中的作用对于掌握编程艺术至关重要。 From 5114cc66fc286f946d123ac7b5b2e72f1ec6fcf6 Mon Sep 17 00:00:00 2001 From: clcs <143490477+clcs@users.noreply.github.com> Date: Thu, 11 Dec 2025 14:30:57 +0800 Subject: [PATCH 07/11] Update 3.md --- sicp/1/3.md | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/sicp/1/3.md b/sicp/1/3.md index 7db7c91..499ae94 100644 --- a/sicp/1/3.md +++ b/sicp/1/3.md @@ -8,33 +8,36 @@ 对应:Lab 01 ::: -我们已经在 Python 中确定了强大的编程语言中一些必须出现的要素: +我们在 Python 中已经看到,一个真正强大的编程语言必须具备的几个核心要素: -1. 原始的内置数据和函数:数字和算术运算 -2. 组合方式:嵌套函数 -3. 受限的抽象方式:将名称与值绑定 +1. 数字和算术运算属于原始的内置数据和函数 +2. 通过函数的**嵌套调用**,我们可以将各种操作组合起来 +3. 通过将**名称绑定到值**上,实现初步的抽象能力 -现在我们来学习 **函数定义**,这是一种更为强大的抽象技术,通过它可以将名称与复合操作绑定为一个单元。 +现在,我们将学习一种更加重要的抽象手段——**函数定义**。通过它,我们可以把一个**复合操作整体绑定**到一个名称上,之后就可以把这个复合操作当作一个整体来反复使用。 -首先来研究一下 **平方** 的概念。我们可能会说:“平方就是一个数乘以它本身。”这在 Python 中可以表示为: +我们先从一个最简单的例子入手:计算平方的操作。我们会说:“要把一个数平方,就把它自己乘以自己。”在 Python 中,这可以写成: ```py >>> def square(x):     return mul(x, x) ``` -上面的代码定义了一个名为 `square` 的新函数,这个用户定义的函数并不会内置到解释器中,它表示将某值与自身相乘的复合运算。这个定义将 `x` 作为被乘的东西的名称,称为 **形式参数**,同时也将此函数与名称 `square` 绑定。 +这条语句定义了一个名叫 `square` 的新函数。这个函数不是解释器自带的,而是我们**自己定义的**。它描述的就是“把某个东西乘以自己”这个复合操作。这里的 `x` 叫做**形式参数**(formal parameter),它为“那个被乘的东西”提供了一个名称。 -**如何定义函数**:函数定义包含 `def` 语句、`` 和一个以逗号分隔的 `` 列表,然后是一个被称为函数体的 `return` 语句,它指定了调用函数时要计算的表达式,也就是函数的 `` : +**如何定义函数**:函数定义由两部分组成: + +1. `def` 语句:指明**函数的名称**和一个以逗号分隔的**形式参数列表** +2. 紧接着缩进的 `return` 语句(称为**函数体**),里面写的是**返回表达式**——每当函数被调用时,这个表达式就会被求值。 ```py -def (): - return +def <函数名>(<形式参数列表>): + return <返回表达式> ``` 函数的第二行 **必须** 进行缩进,大多数程序员使用四个空格。返回表达式会作为新定义的函数的一部分存储,并且仅在最终调用该函数时才进行求值。 -定义了 `square` 之后,我们可以调用它: +定义好 `square` 之后,我们就可以像使用内置函数一样来调用它: ```py >>> square(21) @@ -45,7 +48,7 @@ def (): 81 ``` -我们还可以将 `square` 作为一个构建单元来定义其他函数。例如,我们可以很容易地定义一个函数 `sum_squares`,给定任意两个数字作为参数,返回它们的平方和: +我们还可以把 `square` 当作一个积木,用来搭建更复杂的函数。例如,很轻松地就能定义一个函数 `sum_squares`,它接收两个数,返回这两个数的平方和: ```py >>> def sum_squares(x, y): @@ -55,9 +58,9 @@ def (): 25 ``` -用户定义函数的使用方式与内置函数完全相同。实际上,从 `sum_squares` 的定义中我们并不能判断 `square` 是内置于解释器中,还是从模块中导入的,又或是用户定义的。 +用户自己定义的函数和内置函数在使用上完全没有区别。实际上,光看 `sum_squares` 的定义,你根本看不出 `square` 是解释器自带的、从模块里导入的,还是用户自己写的。 -`def` 语句和赋值语句都将名称与值绑定,并且绑定后任何之前的绑定都将丢失。例如,下面的 `g` 首先指的是一个没有参数的函数,然后是指一个数字,最后是一个含有两个参数的函数。 +无论是 `def` 语句还是赋值语句(`=`),都会把一个名称绑定到一个值上,如果这个名称之前已经绑定过别的东西,旧的绑定就会丢失。例如: ```py >>> def g(): @@ -73,6 +76,8 @@ def (): 3 ``` +可以看到,同一个名称 `g` 在不同时刻可以指向完全不同的东西,后面的绑定会**彻底覆盖**前面的绑定。这正是 Python 动态语言特性的体现。 + ## 1.3.1 环境 虽然我们现在的 Python 子集已经足够复杂,但程序的含义并不明显。如果形参与内置函数同名怎么办?两个函数可以共享名称而不会混淆吗?要解决这些问题,我们必须更详细地描述环境。 From cccc430135e82566284a17559d551efb748bf700 Mon Sep 17 00:00:00 2001 From: clcs <143490477+clcs@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:03:30 +0800 Subject: [PATCH 08/11] Update 3.md --- sicp/1/3.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/sicp/1/3.md b/sicp/1/3.md index 499ae94..0d35650 100644 --- a/sicp/1/3.md +++ b/sicp/1/3.md @@ -1,7 +1,7 @@ # 1.3 定义新的函数 ::: details INFO -译者:[Mancuoj](https://github.com/mancuoj) +译者:[mancuoj](https://github.com/mancuoj)、[clcs](https://github.com/clcs) 来源:[1.3 Defining New Functions](https://www.composingprograms.com/pages/13-defining-new-functions.html) @@ -10,7 +10,7 @@ 我们在 Python 中已经看到,一个真正强大的编程语言必须具备的几个核心要素: -1. 数字和算术运算属于原始的内置数据和函数 +1. **数字和算术运算**属于原始的内置数据和函数 2. 通过函数的**嵌套调用**,我们可以将各种操作组合起来 3. 通过将**名称绑定到值**上,实现初步的抽象能力 @@ -28,14 +28,14 @@ **如何定义函数**:函数定义由两部分组成: 1. `def` 语句:指明**函数的名称**和一个以逗号分隔的**形式参数列表** -2. 紧接着缩进的 `return` 语句(称为**函数体**),里面写的是**返回表达式**——每当函数被调用时,这个表达式就会被求值。 +2. 紧接着缩进的 `return` 语句(称为**函数体**),里面写的是**返回表达式** —— 每当函数被调用时,这个表达式就会被求值。 ```py def <函数名>(<形式参数列表>): return <返回表达式> ``` -函数的第二行 **必须** 进行缩进,大多数程序员使用四个空格。返回表达式会作为新定义的函数的一部分存储,并且仅在最终调用该函数时才进行求值。 +函数的第二行**必须**进行缩进,大多数程序员使用四个空格。返回表达式会作为新定义的函数的一部分存储,并且仅在最终调用该函数时才进行求值。 定义好 `square` 之后,我们就可以像使用内置函数一样来调用它: @@ -80,13 +80,18 @@ def <函数名>(<形式参数列表>): ## 1.3.1 环境 -虽然我们现在的 Python 子集已经足够复杂,但程序的含义并不明显。如果形参与内置函数同名怎么办?两个函数可以共享名称而不会混淆吗?要解决这些问题,我们必须更详细地描述环境。 +现在,我们这个 Python 子集已经复杂到程序的含义不再显而易见了。比如: -求解表达式的环境由 **帧** 序列组成,它们可以被描述为一些盒子。每个帧都包含了一些 **绑定**,它们将名称与对应的值相关联。**全局** 帧(global frame)只有一个。赋值和导入语句会将条目添加到当前环境的第一帧。目前,我们的环境仅由全局帧组成。 +- 如果形式参数的名称和某个内置函数重名,会发生什么? +- 两个函数能不能用同一个名称而不引起混乱? + +为了解答这类问题,我们必须更仔细地说明**环境**(environment)到底是怎么工作的。 + +求解表达式的环境由**帧**序列组成,它们可以被描述为一些盒子。每个帧都包含了一些**绑定**,它们将名称与对应的值相关联。**全局**帧(global frame)只有一个。赋值和导入语句会将条目添加到当前环境的第一帧。目前,我们的环境仅由全局帧组成。 -此 **环境图** 显示了当前环境中的绑定,还有名称和值的绑定。本文中的环境图是交互式的:你可以逐步运行左侧程序的每一行,然后在右侧查看环境状态的演变。你还可以单击“Edit this code”以将示例加载到 [Online Python Tutor](https://www.composingprograms.com/tutor.html) 中,它是由 [Philip Guo](http://www.pgbovine.net/)  创建的用于生成环境图的工具。希望你能够自己去创建示例,研究对应生成的环境图。 +此 **环境图** 显示了当前环境中的绑定,还有名称和值的绑定。本文中的环境图是交互式的:你可以逐步运行左侧程序的每一行,然后在右侧查看环境状态的演变。你还可以单击“Edit this code”以将示例加载到 [Online Python Tutor](https://www.composingprograms.com/tutor.html) 中,它是由 [Philip Guo](http://www.pgbovine.net/) 创建的用于生成环境图的工具。希望你能够自己去创建示例,研究对应生成的环境图。 函数也会出现在环境图中。`import` 语句将名称与内置函数绑定。`def` 语句将名称与用户自定义的函数绑定。导入 `mul` 并定义 `square` 后的结果环境如下所示: From 3fb9b56fcfa0f44ba18603dde89e75693e339c50 Mon Sep 17 00:00:00 2001 From: clcs <143490477+clcs@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:07:02 +0800 Subject: [PATCH 09/11] Update 1.md --- sicp/1/1.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/sicp/1/1.md b/sicp/1/1.md index 2918902..e346b16 100644 --- a/sicp/1/1.md +++ b/sicp/1/1.md @@ -38,8 +38,6 @@ Python 作为一种教学语言非常出色,因为在其整个历史中,Pyth 你可以从 Python 下载页面点击以 3 开头的版本下载 Python 3,并按照安装程序的说明完成安装。 -如需进一步指导,请查看 Julia Oh 创建的有关 Python 3 的 [Windows 安装](http://www.youtube.com/watch?v=54-wuFsPi0w) 和 [Mac 安装](http://www.youtube.com/watch?v=smHuBHxJdK8) 的视频教程。 - ## 1.1.3 交互式会话 在与 Python 的交互式会话中,你可以在提示符 `>>>` 后**键入一些 Python 代码**,Python 解释器会读取并执行你键入的各种命令。 From dc1383c7019178f592be7b6474208aeccb0a102c Mon Sep 17 00:00:00 2001 From: clcs <143490477+clcs@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:39:36 +0800 Subject: [PATCH 10/11] Update 3.md --- sicp/1/3.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/sicp/1/3.md b/sicp/1/3.md index 0d35650..e07d138 100644 --- a/sicp/1/3.md +++ b/sicp/1/3.md @@ -82,34 +82,36 @@ def <函数名>(<形式参数列表>): 现在,我们这个 Python 子集已经复杂到程序的含义不再显而易见了。比如: -- 如果形式参数的名称和某个内置函数重名,会发生什么? +- 如果形式参数的名称和某个内置函数**重名**,会发生什么? - 两个函数能不能用同一个名称而不引起混乱? 为了解答这类问题,我们必须更仔细地说明**环境**(environment)到底是怎么工作的。 -求解表达式的环境由**帧**序列组成,它们可以被描述为一些盒子。每个帧都包含了一些**绑定**,它们将名称与对应的值相关联。**全局**帧(global frame)只有一个。赋值和导入语句会将条目添加到当前环境的第一帧。目前,我们的环境仅由全局帧组成。 +在 Python 中,对一个表达式求值的环境由一系列**帧**(frame)组成,我们通常把帧画成一个个方框。每个帧里存放着一系列的**绑定**(binding):名称 → 对应的值。存在一个单独的**全局帧**(global frame)。赋值和导入语句都会往当前环境的第一个帧(也就是全局帧)里添加绑定。目前,我们的环境只包含全局帧。 -此 **环境图** 显示了当前环境中的绑定,还有名称和值的绑定。本文中的环境图是交互式的:你可以逐步运行左侧程序的每一行,然后在右侧查看环境状态的演变。你还可以单击“Edit this code”以将示例加载到 [Online Python Tutor](https://www.composingprograms.com/tutor.html) 中,它是由 [Philip Guo](http://www.pgbovine.net/) 创建的用于生成环境图的工具。希望你能够自己去创建示例,研究对应生成的环境图。 +**环境图**(environment diagram)显示了当前环境中的绑定,还有名称所绑定的值。本书中的环境图是交互式的:你可以逐步执行左侧程序的每一行,然后在右侧查看**环境的变化**。你还可以单击“Edit this code”以将示例加载到 [Online Python Tutor](https://www.composingprograms.com/tutor.html) 中,它是由 [Philip Guo](http://www.pgbovine.net/) 创建的用于生成环境图的工具。希望你能够自己去创建示例,研究对应生成的环境图。 函数也会出现在环境图中。`import` 语句将名称与内置函数绑定。`def` 语句将名称与用户自定义的函数绑定。导入 `mul` 并定义 `square` 后的结果环境如下所示: -每个函数都是一行,以 `func` 开头,后面是函数名称和形式参数。`mul` 等内置函数没有正式的参数名称,所以都是使用 `...` 代替。 +每个函数都是一行以 `func` 开头的文本,后面紧跟函数名和形式参数。内置函数(如 `mul`)没有明确的形式参数名,所以统一写成 `...`。 -函数名称重复两次,一次在环境帧中,另一次是作为函数定义的一部分。函数定义中出现的名称叫做 **内在名称(intrinsic name)**,帧中的名称叫做 **绑定名称(bound name)**。两者之间有一个区别:不同的名称可能指的是同一个函数,但该函数本身只有一个内在名称。 +函数名称重复出现了两次:一次在环境**帧**中,另一次是作为**函数定义的一部分**。出现在函数定义中的名称叫做**内在名称**(intrinsic name),帧中的名称叫做**绑定名称**(bound name)。两者之间是有区别的:不同的名称可能引用的是同一个函数,但该函数本身**只有一个**内在名称。 -绑定到帧中的函数名称是在求值过程中使用,而内在名称在求值中不起作用。使用 Next 按钮逐步执行下面的示例,可以看到一旦名称 `max` 与数字值 3 绑定,它就不能再用作函数。 +真正参与求值的是帧里绑定的名称,函数的内在名称在求值过程中不起作用。使用 Next 按钮逐步执行下面的示例,可以看到一旦名称 `max` 与数字值 `3` 绑定,它就不能再作为函数使用了。 -错误信息“TypeError: 'int' object is not callable”报告了名称 `max` (当前绑定到数字 3)是一个整数而不是函数,所以它不能用作调用表达式中的运算符。 +错误信息 `TypeError: 'int' object is not callable` 报告了名称 `max`(当前绑定到数字 `3`)是一个整数而不是函数,所以它不能用作调用表达式中的运算符。 -**函数签名**:每个函数允许采用的参数数量有所不同。为了跟踪这些要求,我们绘制了每个函数的名称及其形式参数。用户定义的函数 `square` 只需要 `x` 一个参数,提供或多或少的参数都将导致错误。对函数形式参数的描述被称为函数的签名。 +**函数签名**(Function signatures):不同的函数允许接收的参数个数不同。为了在环境图里清楚地表达这一点,我们会把**函数名 + 形式参数**一起显示,这就是所谓的函数签名。 -函数 `max` 可以接受任意数量的参数,所以它被呈现为 `max(...)`。因为原始函数从未明确定义,所以无论采用多少个参数,所有的内置函数都将呈现为 `(...)`。 +- 用户定义的函数 `square` 只需要 `x` 一个参数,多给或少给都会报错,所以写成:`func square(x)` +- 内置函数 `max` 可以接受任意数量的参数,所以统一写成 `max(...)` +- 实际上,无论接受多少参数,所有的内置函数都将显示为 `(...)`,因为这些原始函数从未被显式定义过。 ## 1.3.2 调用用户定义的函数 From d3e9a408b6e73d8ca858f6daa3639da63c5b4711 Mon Sep 17 00:00:00 2001 From: clcs <143490477+clcs@users.noreply.github.com> Date: Mon, 22 Dec 2025 14:41:47 +0800 Subject: [PATCH 11/11] Update 3.md --- sicp/1/3.md | 131 +++++++++++++++++++++++++++------------------------- 1 file changed, 68 insertions(+), 63 deletions(-) diff --git a/sicp/1/3.md b/sicp/1/3.md index e07d138..eafab21 100644 --- a/sicp/1/3.md +++ b/sicp/1/3.md @@ -113,74 +113,75 @@ def <函数名>(<形式参数列表>): - 内置函数 `max` 可以接受任意数量的参数,所以统一写成 `max(...)` - 实际上,无论接受多少参数,所有的内置函数都将显示为 `(...)`,因为这些原始函数从未被显式定义过。 -## 1.3.2 调用用户定义的函数 +## 1.3.2 调用用户自定义函数 -为了求出操作符为用户定义函数的调用表达式,Python 解释器遵循了以下计算过程。与其他任何调用表达式一样,解释器将对操作符和操作数表达式求值,然后用生成的实参调用具名函数。 +当 Python 解释器对一个指向**用户自定义函数**的调用表达式进行求值时,会遵循一套特定的计算流程。与处理任何调用表达式一样,解释器会先对运算符(operator)和操作数(operand)表达式进行求值,然后将得到的函数应用于相应的参数上。 -调用用户定义的函数会引入局部帧(local frame),它只能被该函数访问。通过一些实参调用用户定义的函数: +调用用户自定义函数会引入第二个局部帧(local frame),该帧仅供该函数访问。将函数应用于参数的具体步骤如下: -1. 在新的局部帧中,将实参绑定到函数的形参上。 -2. 在以此帧开始的环境中执行函数体。 +1. 在一个新的局部帧中,将参数值绑定到函数的形参名上 +2. 在以该局部帧为起点的环境中,执行函数体内容 -求值函数体的环境由两个帧组成:一是包含形式参数绑定的局部帧,然后是包含其他所有内容的全局帧。函数的每个实例都有自己独立的局部帧。 +用于计算函数体的环境由两个帧组成:首先是包含形参绑定的局部帧,其次是包含其他所有内容的全局帧。每一次函数调用都会拥有一个**彼此独立**的局部帧。 -为了详细说明一个例子,下面将会描述相同示例的环境图中的几个步骤。执行第一个 `import` 语句后,只有名称 `mul` 被绑定在全局帧中。 +为了详细展示这一过程,下面通过环境图演示该示例的**执行步骤**。在执行完第一行导入语句后,全局帧中只有名称 `mul` 完成了绑定。 -首先,执行定义函数 `square` 的语句。请注意,整个 `def` 语句是在一个步骤中处理的。直到函数被调用(而不是定义时),函数体才会被执行。 +首先,执行 `square` 函数的定义语句。请注意,整个 `def` 语句是在一个步骤内处理完成的。函数体只有在函数**被调用时才会执行**,在定义阶段并不会运行。 -接下来,使用参数 -2 调用 `square` 函数,它会创建一个新的帧,将形式参数 `x` 与 -2 绑定。 +接下来,调用 `square` 函数并传入参数 `-2`。此时,系统会创建一个新帧,并将形参 `x` 绑定到数值 `-2`。 -然后在当前环境中查找名称 `x`,它由所示的两个帧组成,而在这两种情况下,`x` 的结果都是 -2,因此此 `square` 函数返回 4。 +随后,程序会在当前环境中查找名称 `x`。当前环境由图中所示的两个帧组成。在这两处位置,`x` 的求值结果均为 `-2`,因此 `square` 函数返回结果 4。 -`square()` 帧中的“返回值”不是名称绑定的值,而是调用创建帧的函数返回的值。 +需要注意的是,`square()` 帧中的“返回值(Return value)”并**不是一种名称绑定**,它仅表示创建该帧的函数调用所返回的结果。 -即使在这个简单的示例中,也使用了两个不同的环境。顶级表达式 `square(-2)` 在全局环境中求值,而返回表达式 `mul(x, x)` 在调用 `square` 时创建的环境中求值。虽然 `x` 和 `mul` 都在这个环境中,但在不同的帧中。 +即便在这个简单的例子中,也涉及了两个不同的环境。顶级表达式 `square(-2)` 是在**全局环境中**求值的,而返回表达式 `mul(x, x)` 则是在调用 `square` 时创建的**局部环境**中求值的。虽然 `x` 和 `mul` 在该环境中都有绑定,但它们存在于不同的帧中。 -环境中帧的顺序会影响通过表达式查找名称而返回的值。我们之前说过,名称会求解为当前环境中与该名称关联的值。我们现在可以更准确地说: +环境中**帧的排列顺序**决定了表达式中名称查找的结果。此前我们提到,名称会求值为当前环境中与其关联的那个值。现在我们可以表述得更精准: -**名称求解(Name Evaluation)**:在环境中寻找该名称,最早找到的含有该名称的帧,其里边绑定的值就是这个名称的计算结果。 +**名称求值**(Name Evaluation):名称会求值为当前环境中**最早包含该名称的帧**内所绑定的值。 -环境、名称和函数的概念框架构成了求解模型,虽然一些细节机制仍未指定(例如,如何实现绑定),但我们的模型确实精准地描述了解释器如何求解调用表达式。在第三章中,我们将看到这个模型如何作为蓝图来实现编程语言的工作解释器。 +我们关于环境、名称和函数的这一套概念框架构成了一个完整的**求值模型**。虽然一些底层细节(例如绑定的具体实现方式)尚未详述,但该模型已能精确、正确地描述解释器如何处理调用表达式。在第三章中,我们将看到这一模型如何作为蓝图,帮助我们亲手实现一个可运行的编程语言解释器。 -## 1.3.3 示例:调用用户定义的函数 +## 1.3.3 示例:调用用户自定义函数 -让我们再次思考两个简单的函数定义,并说明计算用户定义函数的调用表达式的过程。 +让我们再次通过这两个简单的函数定义,来演示用户自定义函数调用表达式的求值过程。 -Python 首先求解名称 `sum_squares` ,并将它绑定到全局帧中的用户定义的函数,而原始数值表达式 5 和 12 的计算结果为它们所代表的数字。 +Python 首先对名称 `sum_squares` 进行求值,该名称在全局帧中被绑定到一个用户自定义函数上。同时,原始数值表达式 `5` 和 `12` 分别求值为它们所代表的数字。 -接下来 Python 会调用 `sum_squares` ,它引入了一个局部帧,将 `x` 绑定到 5,将 `y` 绑定到 12。 +接着,Python 调用 `sum_squares`。此时会引入一个局部帧,将 `x` 绑定为 `5`,将 `y` 绑定为 `12`。 -`sum_squares` 的主体包含此调用表达式: +`sum_squares` 的函数体包含如下调用表达式: ``` add ( square(x) , square(y) ) ________ _________ _________ -operator operand 0 operand 1 + 运算符 操作数 0 操作数 1 ``` -所有三个子表达式都在当前环境中计算,且始于标记为 `sum_squares()` 的帧。运算符子表达式 `add` 是在全局帧中找到的名称,它绑定到了内置的加法函数上。在调用加法之前,必须依次求解两个操作数子表达式,两个操作数都在标记为 `sum_squares` 的帧的环境中计算。 +这三个子表达式都在当前环境中进行求值,该环境以标记为 `sum_squares()` 的帧为**起点**。运算符子表达式 `add` 是在全局帧中找到的名称,绑定了内置的加法函数。在执行加法之前,必须依次对两个操作数子表达式进行求值。这两个操作数也都在以 `sum_squares` 帧为起点的当前环境中求值。 -在 `operand 0` 中,`square` 在全局帧中命名了一个用户定义的函数,而 `x` 在局部帧中命名了数字 5。Python 通过引入另一个将 `x` 与 5 绑定的局部帧来调用 `square` 函数。 +在操作数 0 中,`square` 指向全局帧中的一个用户自定义函数,而 `x` 在局部帧中指向数字 `5`。Python 通过**引入另一个局部帧**将 `x` 绑定到 `5`,从而将 `square` 应用于 `5`。 + +在该环境下,表达式 `mul(x, x)` 的求值结果为 `25`。 -此环境下表达式 `mul(x, x)` 的计算结果为 25。 -继续求解 `operand 1`,其中 y 的值为 12。Python 会再次对 `square` 的函数体进行求解,此时引入另一个将 `x` 与 12 绑定的局部帧,计算结果为 144。 +随后,求值程序转向操作数 1。此时 `y` 指向数字 `12`。Python 再次执行 `square` 的函数体,这次又引入了一个新的局部帧,将 `x` 绑定到 `12`。因此,操作数 1 的求值结果为 `144`。 -最后,对参数 25 和 144 调用加法得到 `sum_squares` 的最终返回值:169。 +最后,将加法应用于参数 `25` 和 `144`,得出 sum_squares 的最终返回值:`169`。 -这个例子展示了我们到目前为止学到的许多基本思想。将名称绑定到值,而这些值会分布在多个无关的局部帧,以及包含共享名称的单个全局帧中。每次调用函数时都会引入一个新的局部帧,即使是同一个函数被调用两次。 +这个例子展示了我们目前所构建的许多核心概念。名称被绑定到值上,而这些绑定分布在许多独立的局部帧以及一个包含共享名称的全局帧中。**每当函数被调用时,都会引入一个新的局部帧,即使是同一个函数被调用两次也不例外**。 -所有这些机制的存在都是为了确保名称在程序执行期间的正确时间解析为正确的值。这个例子说明了为什么我们之前介绍了“模型需要复杂性”。所有三个局部帧都包含名称 `x` 的绑定,但该名称会与不同帧中的不同值进行绑定,局部帧会将这些名称分开。 +所有这些机制的建立,都是为了确保在程序执行过程中,名称能够在**正确的时间解析为正确的值**。这个例子也说明了为什么我们的模型需要引入这种复杂性:虽然三个局部帧都包含名称 `x` 的绑定,但 `x` 在**不同帧中绑定的是不同的值**。局部帧有效地实现了这些名称的**隔离**。 ## 1.3.4 局部名称 -实现函数的一个细节就是,实现者为函数的形参选择的名称不应该影响函数行为。所以,以下函数应该提供相同的行为: +函数实现中的一个细节不应影响其行为,那就是实现者为函数的**形参所选取的名称**。因此,以下两个函数的行为应当是完全一致的: ```py >>> def square(x): @@ -189,33 +190,33 @@ operator operand 0 operand 1 return mul(y, y) ``` -一个函数的含义应该与编写者选择的参数名称无关,这个原则对编程语言有重要的意义。最简单的就是函数的参数名称必须在保持函数体局部范围内。 +这一原则——即函数的含义应独立于作者选择的**参数名称**——对编程语言有着深远的影响。最直接的后果就是:函数的参数名称必须仅在该函数体内部**局部有效**。 -如果参数不是它们各自函数体的局部参数,那么 `square` 中的参数 `x` 可能会与 `sum_squares` 中的参数 `x` 混淆。但情况并非如此:`x` 在不同局部帧中的绑定是不相关的。计算模型经过精心设计以确保这种无关性。 +如果参数不是其对应函数体的局部变量,那么 `square` 中的参数 `x` 可能会与 `sum_squares` 中的参数 `x` 混淆。关键在于,这种情况并不会发生:不同局部帧中对 `x` 的绑定是互不干扰的。我们的计算模型经过精心设计,确保了这种独立性。 -局部名称的作用域限于定义它的函数的主体,当一个名称不可再访问时,就是超出了作用域。这种界定作用域的行为并不是我们模型的新细节,而是环境工作方式的结果。 +我们称局部名称的**作用域**(scope)仅限于定义它的用户自定义函数体。当一个名称不再可访问时,它就超出了作用域。这种作用域行为并不是我们模型中的新发现,而是环境运作方式的必然结果。 -## 1.3.5 选择名称 +## 1.3.5 命名规范 -名称的可修改性并不意味着形式参数名称不重要。相反,精心选择的函数和参数名称对于程序的可解释性至关重要! +名称的可互换性并不意味着形参名称无关紧要。恰恰相反,精心挑选的函数名和参数名对于**人类**理解函数定义至关重要。 -以下指南改编自 [Python 代码风格指南](http://www.python.org/dev/peps/pep-0008), 它可以作为所有(非叛逆的)Python 程序员的指南。这些共享的约定使开发者社区的成员之间的沟通能够顺利进行。作为遵循这些约定的副作用,你会发现你的代码在内部变得更加一致。 +以下指南摘自 [Python 代码风格指南 PEP8](http://www.python.org/dev/peps/pep-0008), 它是所有(非叛逆型)Python 程序员的行动指南。一套共同的约定能让开发者社区成员之间的沟通更加顺畅。遵循这些约定,你会发现你的代码内部逻辑也会变得更加一致。 -1. 函数名是小写的,单词之间用下划线分隔。鼓励使用描述性名称。 -2. 函数名称通常反映解释器应用于参数的操作(例如, `print, add, square` )或结果(例如, `max, abs, sum` )。 -3. 参数名称是小写的,单词之间用下划线分隔。首选单个词的名称。 -4. 参数名称应该反映参数在函数中的作用,而不仅仅是允许的参数类型。 -5. 当作用明确时,单字参数名称可以接受,但应避免使用 l(小写的 L)和 O(大写的 o)或 I(大写的 i)以避免与数字混淆。 +1. **函数名**:使用小写字母,单词之间用下划线分隔。鼓励使用具有**描述性**的名称 +2. **函数名含义**:通常反映解释器对参数执行的操作(如 `print、add、square`),或者是返回结果的含义(如 `max、abs、sum`) +3. **参数名**:使用小写字母,单词之间用下划线分隔。优先使用**单个单词**名称 +4. **参数名含义**:应反映参数在函数中的**作用**,而不仅仅是允许传入的参数类型 +5. **单字母参数**:当其作用显而易见时可以接受,但要避免使用 `l`(小写 L)、`O`(大写 O)或 `I`(大写 I),以免与数字混淆 -当然这些准则也有许多例外,即使在 Python 标准库中也是如此。像英语的词汇一样,Python 继承了各种贡献者的词汇,所以结果并不总是一致的。 +即便是在 Python 标准库中,违反这些准则的情况也屡见不鲜。就像英语词汇一样,Python 继承了来自各方贡献者的代码,结果并不总是完全统一。 -## 1.3.6 抽象函数 +## 1.3.6 函数作为抽象工具 -虽然 `sum_squares` 这个函数非常简单,但它体现了用户自定义函数最强大的特性。函数 `sum_squares` 是根据函数 `square` 定义的,但仅依赖于 `square` 在其输入参数和输出值之间定义的关系。 +尽管 `sum_squares` 非常简单,但它体现了用户自定义函数最强大的特性:**函数抽象**(functional abstraction)。`sum_squares` 是基于 `square` 定义的,但它仅依赖于 `square` 定义的输入参数与输出值之间的关系。 -我们可以编写 `sum_squares` 而不用关心如何对一个数求平方。如何计算平方的细节可以被隐藏到之后再考虑。确实,对于 `sum_squares` 而言,`square` 并不是一个特定的函数体,而是一个函数的抽象,也就是所谓的函数抽象(functional abstraction)。在这个抽象层次上,任何计算平方的函数都是等价的。 +我们在编写 `sum_squares` 时,无需关注如何计算一个数的平方。计算平方的具体细节可以被**隐匿**起来,留到以后再考虑。事实上,对于 `sum_squares` 而言,`square` 并不是一段**具体**的函数体,而是**一个函数的抽象**,即所谓的函数抽象。在这个抽象层面上,任何能计算平方的函数都是一样好用的。 -所以在只考虑返回值的情况下,以下两个计算平方数的函数是无法区分的:它们都接受数值参数并返回该数的平方值。 +因此,如果仅考虑返回值,以下两个用于计算平方的函数应该是无差别的。它们都接受一个数值参数,并产出该数的平方。 ```py >>> def square(x): @@ -224,65 +225,69 @@ operator operand 0 operand 1     return mul(x, x-1) + x ``` -换句话说,函数定义能够隐藏细节。用户可能不会自己去编写函数,而是从另一个程序员那里获得它,然后将它作为一个“黑盒”,用户只需要调用,而不需要知道实现该功能的细节。Python 库就具有此属性,许多开发人员使用这里定义的函数,但很少有人去探究它们的实现。 +换句话说,函数定义应该能够**隐藏细节**。函数的使用者可能并没有亲手编写该函数,而是从另一位程序员那里获得的一个**黑盒**。程序员在使用函数时,不应该需要了解其内部实现。Python 标准库就具备这一特性:许多开发者频繁调用其中的函数,但很少有人去查看它们的具体实现。 + +**函数抽象的三个要素**:要精通函数抽象的使用,考虑其三个核心属性通常大有裨益: -**抽象函数的各个方面**:思考抽象函数的三个核心属性通常对掌握其使用很有帮助。**函数的域 domain** 是它可以接受的参数集合;**范围 range** 是它可以返回的值的集合;**意图 intent** 是计算输入和输出之间的关系(以及它可能产生的任何副作用)。通过域、范围和意图来理解函数抽象对之后能在复杂程序中正确使用它们至关重要。 +1. **定义域**(Domain):函数可以接受的参数集合 +2. **值域**(Range):函数可以返回的值的集合 +3. **意图**(Intent):函数在输入与输出之间计算出的关系(以及它可能产生的任何副作用) -例如,我们用于实现 `sum_squares` 的任何平方函数应具有以下属性: +在复杂程序中,通过定义域、值域和意图来理解函数抽象是正确使用它们的关键。例如,我们用于实现 `sum_squares` 的任何 `square` 函数都应具备以下属性: -- **域** 是任意单个实数。 -- **范围** 是任意非负实数。 -- **意图** 是计算输入的平方作为输出。 +- **定义域**:任何单个实数 +- **值域**:任何非负实数 +- **意图**:输出是输入的平方 -这些属性不会描述函数是如何执行的,这个细节已经被抽象掉了。 +这些属性并不规定意图是如何实现的,那些细节已被抽象掉了。 ## 1.3.7 运算符 -数学运算符(例如 + 和 -)为我们提供了组合方法的第一个示例,但我们尚未给包含这些运算符的表达式定义求值过程。 +数学运算符(例如 `+` 和 `-`)是我们接触的第一种**组合方法**示例,但我们尚未为包含这些运算符的表达式定义求值程序。 -带有中缀运算符的 Python 表达式都有自己的求值过程,但你通常可以将它们视为调用表达式的简写形式。当你看到 +Python 中带有**中缀运算符**的表达式都有各自的求值程序,但你通常可以将它们视为调用表达式的简写。当你看到: ```py >>> 2 + 3 5 ``` -可以认为简单地将它理解为以下代码 +只需将其视为以下形式的**简写**: ```py >>> add(2, 3) 5 ``` -中缀表示法可以嵌套,就像调用表达式一样。Python 运算符优先级采用了正常数学规则,它规定了如何求解具有多个运算符的复合表达式。 +中缀表示法可以像调用表达式一样嵌套。Python 遵循**标准**的数学运算符优先级规则,这决定了如何解释含有多个运算符的复合表达式。 ```py >>> 2 + 3 * 4 + 5 19 ``` -它和以下表达式的求解结果完全相同 +其求值结果与下式相同: ```py >>> add(add(2, mul(3, 4)), 5) 19 ``` -调用表达式中的嵌套比运算符版本更加明显,但也更难以阅读。Python 还允许使用括号对子表达式进行分组,用以覆盖正常的优先级规则,或使表达式的嵌套结构更加明显。 +调用表达式的嵌套比运算符版本更明确,但也更难阅读。Python 还允许使用括号对子表达式进行分组,以**覆盖**默认的优先级规则,或使表达式的嵌套结构更加清晰。 ```py >>> (2 + 3) * (4 + 5) 45 ``` -它和以下表达式的求解结果完全相同 +等同于: ```py >>> mul(add(2, 3), add(4, 5)) 45 ``` -对于除法,Python 提供了两个中缀运算符:`/` 和 `//`。前者是常规除法,因此即使除数可以整除被除数,它也会产生 **浮点数**(十进制小数): +在除法方面,Python 提供了两个中缀运算符:`/` 和 `//`。前者是普通除法,即使被除数能被整除,结果也是**浮点数**(小数): ```py >>> 5 / 4 @@ -291,7 +296,7 @@ operator operand 0 operand 1 2.0 ``` -而后一个运算符 `//` 会将结果向下舍入到一个整数: +而 `//` 运算符则会将结果向下取整到整数: ```py >>> 5 // 4 @@ -300,7 +305,7 @@ operator operand 0 operand 1 -2 ``` -这两个运算符算是 `truediv` 和 `floordiv` 函数的简写。 +这两个运算符分别是 `truediv` 和 `floordiv` 函数的简写。 ```py >>> from operator import truediv, floordiv @@ -310,4 +315,4 @@ operator operand 0 operand 1 1 ``` -你可以在程序中随意使用中缀运算符和圆括号。对于简单的数学运算,Python 惯例上更喜欢使用运算符而不是调用表达式。 +在编写程序时,你可以放心使用中缀运算符和括号。在处理简单的数学运算时,地道的 Python 代码(Idiomatic Python)更倾向于使用运算符而非调用表达式。