diff --git a/sicp/1/4.md b/sicp/1/4.md index 4e6db92..db74dfc 100644 --- a/sicp/1/4.md +++ b/sicp/1/4.md @@ -1,70 +1,68 @@ # 1.4 设计函数 ::: details INFO -译者:[Mancuoj](https://github.com/mancuoj) +译者:[mancuoj](https://github.com/mancuoj)、[clcs](https://github.com/clcs) 来源:[1.4 Designing Functions](https://www.composingprograms.com/pages/14-designing-functions.html) 对应:Lab 01 ::: -函数是所有程序(无论大小)的基本组成部分,并且是我们使用编程语言来表达计算过程的主要媒介。之前我们已经讨论过了函数的形式及其调用方式,而本节我们将讨论“什么是一个好函数”。从根本上说,好函数共有的品质就是:它们都强化了“函数就是抽象”的理念。 +无论程序规模大小,**函数**都是其核心组成部分,也是我们在编程语言中表达计算过程的**主要媒介**。到目前为止,我们已经讨论了函数的形式特性以及它们如何被调用。现在,我们将话题转到“什么是优秀的函数”。从根本上说,优秀函数的所有品质都在强化一个核心理念:**函数即抽象**。 -- 每个函数应该只负责一个任务,且该任务要用一个简短的名称来识别,并在一行文本中进行描述。按顺序执行多个任务的函数应该分为多个函数。 -- 不要重复自己(Don't repeat yourself)是软件工程的核心原则。这个所谓的 DRY 原则指出,多个代码片段不应该描述重复的逻辑。相反,逻辑应该只实现一次,为其指定一个名称后多次使用。如果你发现自己正在复制粘贴一段代码,那么你可能已经找到了进行函数抽象的机会。 -- 定义通用的函数。比如作为 `pow` 函数的一个特例的平方函数就不在 Python 库中,因为 `pow` 函数可以将数字计算为任意次方。 +- **每个函数应该只做一件事**。这项工作应该能用一个简短的名称来概括,并能用一行文字描述其特征。如果一个函数按顺序执行多项任务,则应将其拆分为多个函数 +- **不要重复自己**(Don't Repeat Yourself)。这是软件工程的一个核心信条。所谓的 **DRY 原则**指出,多段代码不应描述冗余的逻辑。相反,逻辑应该只实现一次,赋予其名称,然后多次应用。如果你发现自己在复制粘贴一段代码,那么你可能已经找到了一个进行函数抽象的机会 +- **函数的定义应具有通用性**。比如,作为 `pow` 函数的一个**特例**,平方函数就不在 Python 库中,因为 `pow` 函数可以计算任意幂次。 -这些准则提高了代码的可读性,减少了错误的数量,并且通常最大限度地减少了编写的代码总量。将复杂的任务分解为简洁的功能是一项需要经验才能掌握的技能。幸运的是,Python 提供了多种特性来支持你的工作。 +这些准则能提高代码的可读性,减少错误,并通常能减少编写的代码总量。将复杂任务分解为简洁的函数是一项需要经验才能掌握的技能。幸运的是,Python 提供了多项特性来支持你的努力。 ## 1.4.1 文档 -函数定义通常包括描述函数的文档,称为“文档字符串 docstring”,它必须在函数体中缩进。文档字符串通常使用三个引号,第一行描述函数的任务,随后的几行可以描述参数并解释函数的意图: +函数定义通常会包含描述该函数的文档,称为**文档字符串**(docstring)。它必须与函数体**一起缩进**。按照惯例,文档字符串通常使用三引号括起来。第一行用一句话描述函数的功能,随后的段落可以详细说明参数并澄清函数的行为: ```py >>> def pressure(v, t, n): - """计算理想气体的压力,单位为帕斯卡 + """计算理想气体的压力(单位:帕斯卡) 使用理想气体定律:http://en.wikipedia.org/wiki/Ideal_gas_law - v -- 气体体积,单位为立方米 - t -- 绝对温度,单位为开尔文 - n -- 气体粒子 + v -- 气体体积,单位:立方米 + t -- 绝对温度,单位:开尔文 + n -- 气体粒子数 """ k = 1.38e-23 # 玻尔兹曼常数 return n * k * t / v ``` -当你使用函数名称作为参数调用 `help` 时,你会看到它的文档字符串(键入 q 以退出 Python help)。 +当你以函数名作为参数调用 `help` 函数时,就可以看到它的文档字符串(在 Python 帮助界面输入 `q` 退出): ```py >>> help(pressure) ``` -编写 Python 程序时,除了最简单的函数之外,都要包含文档字符串。要记住,虽然代码只编写一次,但是会在之后阅读多次。Python 文档包含了 [文档字符串准则](http://www.python.org/dev/peps/pep-0257/),它会在不同的 Python 项目中保持一致。 +编写 Python 程序时,除了最简单的函数外,应为所有函数编写文档字符串。请记住:代码只写一次,但会被阅读多次。Python 文档包含一套 [文档字符串准则](http://www.python.org/dev/peps/pep-0257/),用于保持不同 Python 项目之间的一致性。 -注释:Python 中的注释可以附加到 `#` 号后的行尾。例如,上面代码中的注释 `玻尔兹曼常数` 描述了 `k` 变量的含义。这些注释不会出现在 Python 的 `help` 中,而且会被解释器忽略,它们只为人类而存在。 +**注释**:Python 中的注释可以附在行尾,紧跟在 `#` 符号后面。例如,上面的注释“玻尔兹曼常数”描述了变量 `k`。这些注释永远不会出现在 Python 的 `help` 信息中,也会被解释器忽略。它们**仅供人类阅读**。 -## 1.4.2 参数默认值 +## 1.4.2 默认参数值 -定义通用函数的结果是引入了额外的参数。具有许多参数的函数可能调用起来很麻烦并且难以阅读。 +定义通用函数的一个后果是会引入额外的参数。参数过多的函数调用起来可能很别扭,且难以阅读。 -在 Python 中,我们可以为函数的参数提供默认值。当调用该函数时,具有默认值的参数是可选的。如果未提供,则将默认值绑定到形参上。例如,如果程序通常用于计算“一摩尔”粒子的压力,则可以提供此值作为默认值: +在 Python 中,我们可以为函数的参数提供**默认值**。调用该函数时,带有默认值的参数是**可选**的。如果没有提供该参数,则默认值将绑定到相应的形参名上。例如,如果一个应用经常计算一摩尔粒子的压强,可以将这个值设为默认值: ```py >>> def pressure(v, t, n=6.022e23): - """计算理想气体的压力,单位为帕斯卡 + """计算理想气体的压强(单位:帕斯卡)。 - 使用理想气体定律:http://en.wikipedia.org/wiki/Ideal_gas_law - - v -- 气体体积,单位为立方米 - t -- 绝对温度,单位为开尔文 - n -- 气体粒子,默认为一摩尔 + v -- 气体体积,单位:立方米 + t -- 绝对温度,单位:开尔文 + n -- 气体粒子数(默认值:一摩尔) """ k = 1.38e-23 # 玻尔兹曼常数 return n * k * t / v ``` -`=` 符号在此示例中表示两种不同的含义,具体取决于使用它的上下文。在 def 语句中,`=` 不执行赋值,而是指示调用 `pressure` 函数时使用的默认值。相比之下,函数体中对 `k` 的赋值语句中将名称 `k` 与玻尔兹曼常数的近似值进行了绑定。 +在这个例子中,`=` 符号根据其使用的语境有两种不同的含义。在 `def` 语句的标题(header)中,`=` 不执行赋值操作,而是表示调用 `pressure` 函数时使用的默认值。相比之下,函数体内部对 `k` 的赋值语句则是将名称 `k` 绑定到玻尔兹曼常数的近似值上。 ```py >>> pressure(1, 273.15) @@ -73,6 +71,6 @@ 6809.924502 ``` -`pressure` 函数的定义接收三个参数,但上面的第一个调用表达式中只提供了两个。在这种情况下,`n` 的值取自 `def` 语句中的默认值。如果提供了第三个参数,默认值将被忽略。 +`pressure` 函数定义为接受三个参数,但在上面的第一个调用表达式中只提供了两个。在这种情况下,`n` 的值取自 `def` 语句中的默认值。如果提供了第三个参数,则默认值会被忽略。 -作为准则,函数主体中使用的大多数数据值都应该表示为具名参数(named arguments)的默认值,这样会使它们更易于检查,并且可以被函数调用者更改。一些永远不会改变的值,例如基本常量 `k` 可以绑定在函数体或全局帧中。 +作为一条准则,函数体中用到的大多数数据都应表达为形参的默认值,以便于查看,也方便函数调用者进行更改。至于一些永远不会改变的值(如基础常数 `k`),则可以绑定在函数体内或全局帧中。 diff --git a/sicp/1/5.md b/sicp/1/5.md index 091f23e..ed5f602 100644 --- a/sicp/1/5.md +++ b/sicp/1/5.md @@ -1,94 +1,92 @@ # 1.5 控制 ::: details INFO -译者:[Mancuoj](https://github.com/mancuoj) +译者:[mancuoj](https://github.com/mancuoj)、[clcs](https://github.com/clcs) 来源:[1.5 Control](https://www.composingprograms.com/pages/15-control.html) 对应:Lab 01 ::: -我们现在可以定义的函数的能力十分有限,因为我们还没有引入一种方法来进行比较,并根据比较的结果执行不同的操作。控制语句将赋予我们这种能力,就是根据逻辑比较的结果来控制程序执行流程的语句。 +到目前为止,我们定义的函数表达能力还非常有限,因为我们还没有引入**比较**(comparison)机制,也无法根据比较结果执行不同的操作。**控制语句**(Control statements)将赋予我们这种能力。这类语句能够根据逻辑比较的结果来控制程序的执行流程。 -语句与我们目前研究过的表达式有着根本的不同,它们没有值。执行一个控制语句决定了解释器接下来应该做什么,而不是计算某些东西。 +**语句**(Statements)与我们之前研究的**表达式**(Expressions)有着本质的区别。语句没有“值(value)”。执行控制语句的目的不是为了计算出某个结果,而是**决定解释器接下来应该做什么**。 ## 1.5.1 语句 -到目前为止,我们虽然主要思考的是如何计算求解表达式,但我们已经见过了三种语句:赋值(assignment)、 `def` 和 `return` 语句。尽管这些 Python 代码都包含表达式作为它们的一部分,但它们本身并不是表达式。 +此前,我们主要关注的是如何对表达式求值。不过,我们其实已经见过三种语句了:赋值语句、`def` 语句和 `return` 语句。这些 Python 代码行本身并不是表达式,尽管它们都包含表达式作为组成部分。 -语句不会被求解,而会被执行。每个语句都描述了对解释器状态的一些更改,并且执行语句就会应用该更改。正如我们在 `return` 和赋值语句中看到的那样,执行语句可能涉及求解其包含的子表达式。 +语句不是被“求值”,而是被执行。每条语句都描述了对解释器状态的某种改变,执行语句就是应用这种改变。正如我们在 `return` 和赋值语句中看到的,执行语句可能涉及对其内部包含的子表达式进行求值。 -表达式也可以作为语句执行,在这种情况下,它们会被求值,但它们的值会被丢弃。执行纯函数没有效果,但执行非纯函数会因为调用函数而产生效果。 +表达式也可以作为语句执行,在这种情况下,它们会被求值,但其求出的值会**被丢弃**。执行一个纯函数(pure function)不会产生任何影响,但执行一个非纯函数(non-pure function)则可能因为函数的应用而产生副作用。 -思考一下,例如: +例如: ```py >>> def square(x): - mul(x, x) # 小心!此调用不返回值。 + mul(x, x) # 注意!这个调用没有任何返回值 ``` -这个例子是有效的 Python 代码,但可能不能达到预期。函数体由一个表达式组成。表达式本身是一个有效的语句,但语句的效果是调用 `mul` 函数,然后把结果丢弃。如果你想对表达式的结果做些什么,你需要用赋值语句存储它或用 `return` 语句返回它: +这个例子在 Python 中是合法的,但可能不符合原意。该函数体由一个表达式组成。表达式本身是一个合法的语句,但该语句的效果仅仅是调用了 `mul` 函数,然后结果就被丢弃了。如果你想对表达式的结果做点什么,必须明确表达:你可以通过赋值语句将其存储起来,或者通过 `return` 语句将其返回: ```py >>> def square(x): return mul(x, x) ``` -有时,在调用 `print` 等非纯函数时,拥有一个主体为表达式的函数确实有意义。 +有时,当调用像 `print` 这样的非纯函数时,函数体只有一个表达式是有意义的: ```py >>> def print_square(x): print(square(x)) ``` -在最高层级上,Python 解释器的工作是执行由语句组成的程序。然而,很多有趣的计算工作都来自对表达式的求值。语句用来管理程序中不同表达式之间的关系,以及它们产生的结果。 +从最高层面来说,Python 解释器的工作就是执行由语句构成的程序。然而,计算过程中大部分有趣的工作都来自对表达式的求值。语句的作用是管理程序中不同表达式之间的关系,以及处理这些表达式的结果。 ## 1.5.2 复合语句 -通常,Python 代码是一系列语句。简单语句是不以冒号结尾的单行,而由其他语句(简单语句和复合语句)组成被称为复合语句。复合语句通常跨越多行,以单行头部(header)开始,并以冒号结尾,其中冒号标识语句的类型。头部和缩进的句体(suite)一起称为子句。复合语句由一个或多个子句组成: +通常,Python 代码是一个语句序列。**简单语句**(simple statement)是不以冒号结尾的单行代码。而**复合语句**(compound statement)是由其他语句(简单的或复合的)组合而成。复合语句通常跨越两行或多行,并以一个以冒号结尾的单行头部(header)开始,该头部标识了语句的类型。一个头部及其下方缩进的“语句体”(suite)合称为一个子句(clause)。一个复合语句可以由一个或多个子句组成: ```py -
: - - +<头部>: + <语句> + <语句> ... -: - - +<分隔头部>: + <语句> + <语句> ... ... ``` 我们可以用这些术语来理解我们之前介绍过的语句。 -- 表达式、返回语句和赋值语句都是简单语句。 -- `def` 语句是复合语句,`def` 头后面的句体定义了函数体。 +- **表达式、`return` 语句和赋值语句**都是简单语句 +- `def` 语句是一个复合语句。紧跟在 `def` 头部之后的语句体定义了函数体 -对每类 header 都有专门的求值规则来规定其何时执行以及是否执行其句体中的语句。我们说“the header controls its suite”,例如,在 `def` 语句中,`return` 表达式不会立即求值,而是存储起来供以后调用该函数时使用。 +每种头部的特定求值规则决定了其语句体在何时执行,甚至是否执行。我们称之为**头部控制(controls)其语句体**。例如,在 `def` 语句中,我们看到返回表达式并不会立即求值,而是被存储起来,直到该函数最终被调用时才执行。 -我们现在也可以理解多行程序了。 +现在我们也可以理解多行程序了:要执行一个语句序列,首先执行第一个语句。如果该语句没有重定向控制流,则继续执行序列中剩余的语句(如果有)。 -- 要执行一系列语句,会先执行第一个语句。如果该语句不重定向控制,则继续执行语句序列的其余部分(如果还有的话)。 +这个定义揭示了**递归定义序列**的核心结构:一个序列可以分解为它的第一个元素和其余元素。而一个语句序列的“其余部分”,本身也是一个语句序列!因此,我们可以递归地应用这条执行规则。这种**将序列视为递归数据结构**的视角,在后续章节中还会再次出现。 -这个定义揭示了递归定义序列(sequence)的基本结构:一个序列可以分解成它的第一个元素和其余元素。语句序列的“其余部分”本身也是语句序列!因此,我们可以递归地应用这个执行规则。这种将序列视为递归的数据结构的观点将在后面的章节中再次出现。 +这条规则产生的一个重要结果是:尽管语句是按顺序执行的,但由于控制流的重定向,后面的语句可能永远不会被执行到。 -此规则的重要结论是语句会按顺序执行,但由于重定向控制(redirected control),后面的语句可能永远不会被执行到。 - -实践指南:缩进句体时,所有行必须以相同的方式缩进相同的量(使用空格,而不是制表符)。缩进的任何变化都会导致错误。 +**实践指导**:在对语句体进行缩进时,所有行必须缩进相同的幅度,并使用相同的方式(请使用空格,不要使用制表符 Tab)。缩进的任何细微差异都会导致语法错误。 ## 1.5.3 定义函数 II:局部赋值 -最初,我们声明用户定义函数的主体仅由包含单个返回表达式的 `return` 语句组成。事实上,函数可以定义超出单个表达式的一系列操作。 +最初我们提到,用户自定义函数的主体仅由一个带有单一表达式的 `return` 语句组成。实际上,函数可以定义一系列操作,其范围远不止一个表达式。 -每当用户定义的函数被调用时,其句体中的子句序列将会在局部环境中执行 --> 该环境通过调用函数创建的局部帧开始。`return` 语句会重定向控制:每当执行一个 `return` 语句时,函数应用程序就会终止,`return` 表达式的值会作为被调用函数的返回值。 +每当调用用户自定义函数时,其定义中的**语句序列**(suite)会在一个**局部环境**中执行——这个环境始于由函数调用创建的**局部帧**(local frame)。`return` 语句会重定向控制流:一旦执行到第一个 `return` 语句,函数调用的过程就会终止,而 `return` 表达式的值就是该函数的返回值。 -赋值语句可以出现在函数体内。例如,以下函数使用了两步计算,首先计算两个数的差的绝对值,然后求出它与第一个数的百分比值并返回: +赋值语句可以出现在函数体内部。例如,下面这个函数通过两步计算,返回两个数值之差绝对值占第一个数的百分比: -赋值语句的作用是将名称与当前环境中的第一帧的值绑定。因此,函数体内的赋值语句不会影响全局帧。“函数只能操纵其局部帧”是创建模块化程序的关键,而在模块化程序中,纯函数仅通过它们接收和返回的值与外界交互。 +赋值语句的作用是将一个名字绑定到当前环境**第一个帧**(first frame)中的值。因此,函数体内的赋值语句不会影响全局帧。函数只能操作其局部环境,这一特性对于**构建模块化程序**至关重要。在模块化程序中,纯函数仅通过它们接收的参数和返回的值进行交互。 -当然, `percent_difference` 函数可以写成单个表达式,如下所示,但返回表达式会更复杂。 +当然,`percent_difference` 函数也可以写成单一表达式的形式(如下所示),但这样 `return` 表达式会变得更加复杂。 ```py >>> def percent_difference(x, y): @@ -97,44 +95,43 @@ 25.0 ``` -到目前为止,局部赋值并没有增强函数定义的表达能力,而当它与其他控制语句结合时,就会增强。此外,局部赋值在“通过为中间量赋名来解释复杂表达式的含义”方面也起着至关重要的作用。 +到目前为止,局部赋值还没有增加函数定义的表达能力。但当它与其他控制语句结合时,其威力就会显现。此外,局部赋值通过为中间量命名,在理清复杂表达式的含义方面也起着关键作用。 ## 1.5.4 条件语句 -Python 有一个用于计算绝对值的内置函数。 +Python 有一个用于计算绝对值的内置函数: ```py >>> abs(-2) 2 ``` -我们希望能够自己实现这样一个函数,但是没有清晰的方法来定义一个具有比较和选择的函数。我们想表达的是,如果 `x` 为正,则 `abs(x)` 返回 `x` ;此外,如果 `x` 为 0,则 `abs(x)` 返回 0;否则,`abs(x)` 返回 `-x`。在 Python 中,我们可以用条件语句来表达这种选择。 +我们希望能够自己实现这样一个函数,但是没有清晰的方法来定义一个包含“比较”和“选择”的函数。我们想表达的是,如果 `x` 为正,则 `abs(x)` 返回 `x` ;此外,如果 `x` 为 0,则 `abs(x)` 返回 0;否则,`abs(x)` 返回 `-x`。在 Python 中,我们可以用**条件语句**(conditional statement)来表达这种选择。 -这个 `absolute_value` 函数的实现提出了几个重要的问题: +这个 `absolute_value` 的实现引出了几个重要概念: -条件语句(Conditional statement):Python 中的条件语句由一系列头部和句体组成:必需的 `if` 子句、可选的 `elif` 子句序列,最后是可选的 `else` 子句: +**条件语句**:Python 中的条件语句由一系列“头部”(header)和“语句体”(suite)组成:一个必需的 `if` 子句,若干可选的 `elif` 子句,以及最后可选的 `else` 子句: ```py -if : - -elif : - +if <表达式>: + <语句体> +elif <表达式>: + <语句体> else: - + <语句体> ``` -执行条件语句时,每个子句都会按顺序被考虑。执行条件子句的计算过程如下。 - -1. 求解头部的表达式 -2. 如果它是真值,则执行该句体。然后,跳过条件语句中的所有后续子句。 +执行条件语句时,每个子句会按顺序被考虑。执行一个条件子句的过程如下: -如果到达 `else` 子句(仅当所有 `if` 和 `elif` 表达式的计算结果为假值时才会发生),则执行其句体。 +1. 计算头部表达式的值 +2. 如果结果为真值(true value),则执行该语句体。随后,跳过条件语句中所有后续的子句 +3. 如果到达了 `else` 子句(仅当所有 `if` 和 `elif` 表达式的计算结果为假值时才会发生),则执行其语句体 -布尔上下文(Boolean contexts):上面,执行过程提到了“假值 a false value”和“真值 a true value”。条件块头部语句内的表达式被称为布尔上下文:它们值的真假对控制流很重要,另外,它们的值不会被赋值或返回。Python 包含多个假值,包括 0、 `None` 和布尔值 `False`,所有其他数字都是真值。在第二章中,我们将看到 Python 中的每种内置数据都具有真值和假值。 +**布尔上下文**(Boolean contexts):上述执行流程中提到了“假值”和“真值”。条件块头部语句中的表达式处于布尔上下文中:它们的真假值决定了控制流,除此之外,它们的值既不会被赋值也不会被返回。Python 中包含若干“假值”,包括 `0`、`None` 以及布尔值 `False`。所有其他数字均为真值。在第 2 章中,我们将看到 Python 中的每种内置数据类型都既有真值也有假值。 -布尔值(Boolean values):Python 有两个布尔值,分别叫做 `True` 和 `False` 。布尔值表示逻辑表达式中的真假值。内置的比较运算符 >, <, > =, <=, ==, != 会返回这些值。 +**布尔值**(Boolean values):Python 有两个布尔值,分别是 `True` 和 `False` 。布尔值在逻辑表达式中代表真值。内置的比较操作 `>`, `<`, `>=`, `<=`, `==`, `!=` 都会返回这些值。 ```py >>> 4 < 2 @@ -150,9 +147,9 @@ True True ``` -最后一个示例读作“0 等于 -0”,对应于 `operator` 模块中的 `eq`。请注意,Python 会区分赋值符号 `=` 与相等比较符号 `==`,这也是许多编程语言共享的约定。 +最后一个示例读作“0 等于 -0”,对应于 `operator` 模块中的 `eq`。请注意,Python 会区分赋值符号 `=` 与相等比较符号 `==`,这也是许多编程语言共有的约定。 -布尔运算符(Boolean operators):Python 中还内置了三个基本的逻辑运算符: +**布尔运算符**(Boolean operators):Python 还内置了三个基础逻辑运算符: ```py >>> True and False @@ -163,89 +160,85 @@ True True ``` -逻辑表达式具有相应的求值过程。而这些过程利用了这样一个理论 --> 有时,逻辑表达式的真值可以在不对其所有子表达式求值的情况下确定,这一特性称为短路(short-circuiting)。 +逻辑表达式有相应的求值程序。这些程序利用了这样一个特性:逻辑表达式的真假值有时无需计算所有子表达式即可确定,这种特性被称为**短路**(short-circuiting)。 --- -求解表达式 ` and ` 的步骤如下: +计算 ` and `: -1. 求解子表达式  ``。 -2. 如果左边的结果为假值 v,则表达式的计算结果就是 v。 -3. 否则,表达式的计算结果为子表达式 `` 的值。 +1. 计算子表达式 `` +2. 如果结果是假值 v,则整个表达式的值就是 v +3. 否则,表达式的计算结果为子表达式 `` 的值 --- -求解表达式  ` or ` 的步骤如下: +计算 ` or `:  -1. 求解子表达式  ``。 -2. 如果左边的结果为真值 v,则表达式的计算结果就是 v。 +1. 计算子表达式  `` +2. 如果结果为真值 v,则表达式的计算结果就是 v 3. 否则,表达式的计算结果为子表达式 `` 的值。 --- -求解表达式  `not ` 的步骤如下: +计算 `not `: -1. 求解  ``,如果结果为假值,则值为 `True` ,否则为 `False`。 +1. 计算 ``,如果结果为假值,则值为 `True`;否则为 `False` --- -这些值、规则和运算符为我们提供了一种组合比较结果的方法。执行比较并返回布尔值的函数通常以 `is` 开头,后面不跟下划线(例如 `isfinite, isdigit, isinstance` 等)。 +这些值、规则和运算符为我们提供了组合比较结果的方法。执行比较并返回布尔值的函数通常以 `is` 开头且不加下划线(例如 `isfinite`, `isdigit`, `isinstance` 等)。 ## 1.5.5 迭代 -除了选择要执行的语句外,控制语句还用于重复。如果我们编写的每一行代码只执行一次,那么编程将是一项非常低效的工作。只有通过重复执行语句,我们才能释放计算机的全部潜力。我们之前已经见过了一种重复形式:一个函数只用定义一次,就可以被多次调用。迭代控制(Iterative control)结构是另一种多次执行相同语句的机制。 +除了选择执行哪些语句外,控制语句还用于表达**重复**。如果我们编写的每一行代码只执行一次,那么编程将是一项非常低效的工作。只有通过重复执行语句,我们才能释放计算机的全部潜力。我们之前已经见过了一种重复形式:一个函数只需定义一次,就可以被多次调用。**迭代控制结构**是另一种多次执行相同语句的机制。 -思考斐波那契数列,其中每个数都是前两个数的和: +考虑斐波那契数列(Fibonacci numbers),其中每个数字都是前两个数字之和: $0, 1, 1, 2, 3, 5, 8, 13, 21, \cdots$ -每个值都是通过重复应用 `sum-previous-two` 的规则构建的,第一个和第二个值固定为 0 和 1。 +每个值都是通过重复应用“前两项求和”的规则构造的,第一个和第二个值固定为 0 和 1。例如,第八个斐波那契数是 13。 + +我们可以使用 `while` 语句来列举 n 个斐波那契数。我们需要跟踪已经创建了多少个值(`k`),和第 k 个值(`curr`)及其前驱(`pred`)。 -我们可以使用 `while` 语句来枚举 n 项斐波那契数列。我们需要跟踪已经创建了多少个值(`k`),和第 k 个值(`curr`)及其前身(`pred`)。单步执行此函数并观察斐波那契数如何一个一个地演化,并绑定到 curr。 +单步执行此函数并观察斐波那契数如何逐一生成,并绑定到 `curr`: -请记住,单行赋值语句可以用逗号分隔多个名称和值同时赋值。该行: +请记住,单行赋值语句可以用**逗号分隔**多个名称和值**同时赋值**。这行代码: `pred, curr = curr, pred + curr` -将名称 `pred` 重新绑定到 `curr` 的值,同时将 `curr` 重新绑定到 `pred + curr` 的值。所有 `=` 右侧的所有表达式都会在绑定之前计算出来。 +将名称 `pred` 重新绑定到 `curr` 的值,同时将 `curr` 重新绑定到 `pred + curr` 的值。在执行任何绑定操作之前,`=` 右侧的所有表达式都会先求值。这种先后顺序——**在更新左侧任何绑定之前计算右侧的所有内容**——对于该函数的正确性至关重要。 -在更新左侧的绑定之前求出所有 `=` 右侧的内容 --> 这种事件顺序对于此函数的正确性至关重要。 - -`while` 子句包含一个头部表达式,后跟一个句体: +`while` 子句包含一个头部表达式,后跟一个语句体: ```py -while : - +while <表达式>: + <语句体> ``` -要执行 `while` 子句: - -1. 求解头部的表达式。 -2. 如果是真值,则执行后面的句体,然后返回第 1 步。 - -在第 2 步中,`while` 子句的整个句体在再次计算头部表达式之前执行。 +执行 `while` 子句的步骤: -为了防止 `while` 子句的句体无限期地执行,句体应该总是在每次循环中更改一些绑定。 +1. 计算头部表达式的值 +2. 如果是真值,执行语句体,然后返回步骤1 -不会终止的 `while` 语句被称为无限循环(infinite loop)。按 `-C` 可以强制 Python 停止循环。 +在步骤 2 中,在再次计算头部表达式之前,会执行 `while` 子句的整个语句体。为了防止 while 语句体无限期地执行,语句体应该在每次循环中**改变**某些绑定。不终止的 while 语句被称为**死循环**(infinite loop)。按下 `-C` 可以强制 Python 停止循环。 ## 1.5.6 测试 -测试一个函数就是去验证函数的行为是否符合预期。现在我们的函数语句已经足够复杂,所以我们需要开始测试我们的实现的函数功能。 +测试一个函数就是去验证函数的行为是否符合预期。我们现在的函数语言已经足够复杂,需要开始对实现进行测试了。 -测试是一种系统地执行验证的机制。它通常采用另一个函数的形式,其中包含对一个或多个对被测试函数的调用样例,然后根据预期结果验证其返回值。与大多数旨在通用的函数不同,测试需要选择特定参数值,并使用它们验证函数调用。测试也可用作文档:去演示如何调用函数,以及如何选择合适的参数值。 +测试是一种系统地执行验证的机制。测试通常以另一个函数的形式存在,该函数包含对被测函数的一个或多个示例调用,然后根据预期结果验证返回值。与大多数旨在通用的函数不同,测试涉及**选择并验证**具有特定参数值的调用。测试还具有**文档**作用:它们演示了如何调用函数以及哪些参数值是合适的。 -断言(Assertions):程序员使用 `assert` 语句来验证是否符合预期,例如验证被测试函数的输出。`assert` 语句在布尔上下文中有一个表达式,后面是一个带引号的文本行(单引号或双引号都可以,但要保持一致),如果表达式的计算结果为假值,则显示该行。 +**断言**(Assertions):程序员使用 `assert` 语句来验证是否符合预期,例如验证被测试函数的输出。`assert` 语句包含一个处于布尔上下文中的表达式,后跟一行引用的文本(单引号或双引号均可),如果表达式求值为假,则会显示该文本。 ```py >>> assert fib(8) == 13, '第八个斐波那契数应该是 13' ``` -当被断言的表达式的计算结果为真值时,执行断言语句无效。而当它是假值时,`assert` 会导致错误,使程序停止执行。 +当断言的表达式为真时,执行 `assert` 语句没有任何效果。当它为假时,`assert` 会引发一个错误并停止执行。 -fib 的测试函数应该测试几个参数,包括 n 的极限值。 +`fib` 的测试函数应该测试几个参数,包括 `n` 的极限值。 ```py >>> def fib_test(): @@ -254,9 +247,9 @@ fib 的测试函数应该测试几个参数,包括 n 的极限值。     assert fib(50) == 7778742049, '在第五十个斐波那契数发生 Error' ``` -当在文件中而不是直接在解释器中编写 Python 时,测试通常是在同一个文件或带有后缀 `_test.py` 的相邻文件中编写的。 +当在文件中编写 Python 代码(而不是直接在解释器中)时,测试通常写在同一个文件中,或者后缀为 `_test.py` 的相邻文件中。 -文档测试(Doctests):Python 提供了一种方便的方法,可以将简单的测试直接放在函数的文档字符串中。文档字符串的第一行应该包含函数的单行描述,接着是一个空行,下面可能是参数和函数意图的详细描述。此外,文档字符串可能包含调用该函数的交互式会话示例: +**文档测试**(Doctests):Python 提供了一种便捷的方法,可以将简单的测试直接放在函数的**文档字符串**(docstring)中。文档字符串的第一行应包含函数的简要描述,后跟一个空行,之后可以是对参数和行为的详细描述。此外,文档字符串还可以包含调用该函数的**交互式示例**: ```py >>> def sum_naturals(n): @@ -273,7 +266,7 @@ fib 的测试函数应该测试几个参数,包括 n 的极限值。 return total ``` -然后,可以通过 [doctest 模块](http://docs.python.org/py3k/library/doctest.html) 来验证交互,如下。 +然后,可以通过 [doctest 模块](http://docs.python.org/py3k/library/doctest.html) 来验证交互: ```py >>> from doctest import testmod @@ -281,7 +274,7 @@ fib 的测试函数应该测试几个参数,包括 n 的极限值。 TestResults(failed=0, attempted=2) ``` -如果仅想验证单个函数的 doctest 交互,我们可以使用名为 `run_docstring_examples` 的 `doctest` 函数。不幸的是,这个函数调用起来有点复杂。第一个参数是要测试的函数;第二个参数应该始终是表达式 `globals()` 的结果,这是一个用于返回全局环境的内置函数;第三个参数 `True` 表示我们想要“详细”输出:所有测试运行的目录。 +如果只想验证单个函数的文档测试,我们可以使用 doctest 模块中名为 `run_docstring_examples` 的函数。这个函数的调用方式稍微有点复杂:第一个参数是要测试的函数;第二个参数应始终是 `globals()`(这是一个返回全局环境的内置函数);第三个参数是 `True`,表示我们希望看到“详细”输出(即所有运行测试的目录)。 ```py >>> from doctest import run_docstring_examples @@ -299,12 +292,12 @@ Expecting: ok ``` -当函数的返回值与预期结果不匹配时,`run_docstring_examples` 函数会将此问题报告为测试失败。 +当函数的返回值与预期结果不符时,`run_docstring_examples` 函数会将该问题报告为一次测试失败。 -当你在文件中编写 Python 时,可以通过使用 doctest 命令行选项启动 Python 来运行文件中的所有 doctest: +在文件中编写 Python 代码时,可以通过在启动 Python 时添加 `doctest` 命令行选项来运行文件中的所有文档测试: ```sh python3 -m doctest ``` -有效测试的关键是在实现新功能后立即编写(并运行)测试。在实现之前编写一些测试也是一种很好的做法,以便在你的脑海中有一些示例输入和输出。调用单个函数的测试称为单元测试(unit test)。详尽的单元测试是良好程序设计的标志。 +有效测试的关键是在实现新函数后**立即编写(并运行)测试**。在实现之前编写一些测试甚至是一个好习惯,这样可以让你心中有明确的输入和输出示例。仅针对单个函数的测试称为**单元测试**(unit test)。详尽的单元测试是优秀程序设计的标志。