Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions sicp/2/1.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,32 @@
对应:Lab 04、Cats
:::

在第一章中,我们主要讨论了计算过程和函数在程序设计中的作用。我们学习了怎么使用原始数据(数字)和原始操作(算术),怎么通过组合和控制形成复合函数,以及怎么通过给程序命名来创建函数抽象。我们还了解到:高阶函数使我们能够根据通用的计算方法进行操作和推理,从而增强了语言的功能。这就是编程的本质
第一章聚焦于**计算过程**与**函数的作用**:用原始数据与原始操作构造表达式,用组合与控制搭建复合函数,用命名建立抽象,并感受高阶函数在提炼通用模式时的威力。编程的核心是把复杂过程拆解成清晰的组合和抽象

本章重点介绍数据:我们研究的技术使我们可以表示和操作许多不同领域的信息。由于互联网的爆炸式增长,所有人都可以在网上免费获取大量结构化信息,并且计算也可以应用于范围广泛的不同问题。有效使用内置数据类型和用户定义的数据类型是数据处理型应用(data processing applications)的基础
这一章的主角换成了**数据**。我们要学习如何表示、组合、抽象和处理各种信息。互联网让海量结构化数据随手可得,计算也因此渗透到几乎所有领域。掌握内置数据类型并能自定义数据类型,是所有数据处理应用的基石

## 2.1.1 原始数据类型

Python 中的每个值都有一个类(class)来确定它的类型。拥有相同类的值,行为也相同。例如,整数 1 和 2 都是 `int` 类的实例,我们就可以使用相似的方法进行处理。例如,它们都可以取反或与另一个整数相加。内置的 `type` 函数允许我们检查任何值的类
每个 Python 值都有一个类(class)来确定其类型。同一类的值行为一致,例如整数 1 和 2 都是 `int`,可以用相同方式运算。内置的 `type` 函数能查看任意值的类

```py
>>> type(2)
<class 'int'>
```

到目前为止,我们使用的值都是 Python 语言中内置的少量的原始数据类型的实例。原始数据类型具有以下属性
我们目前用到的几乎都是内置的少量**原始数据类型**。它们的共同点

1. 有一些可以求解为原始数据类型的表达式,被称为字面量(literals)。
2. 有用于操作原始类型值的内置函数和操作符
1. 有能直接求值的**字面量**(literal)。
2. 语言内置了操作该类型的函数与运算符

`int` 类是用于表示整数的原始数据类型。整数字面量(相邻的数字序列)会求解为 `int` 值,并且数学运算符可以操作这种值
例如,`int` 表示整数。整数字面量求值后就是 `int`,算术运算符可直接作用其上

```py
>>> 12 + 3000000000000000000000000
3000000000000000000000012
```

Python 包含三种原始数字类型:整数(`int`)、浮点数(`float`)和复数(`complex`)。
Python 还内置两种数字类型:浮点数(`float`)和复数(`complex`)。

```py
>>> type(1.5)
Expand All @@ -42,9 +42,9 @@ Python 包含三种原始数字类型:整数(`int`)、浮点数(`float`
<class 'complex'>
```

浮点数:“Float”这个名字来源于 Python 和许多其他编程语言中对实数的表示方式:就是“具有浮动的小数点”的值。虽然关于数字如何表示的细节不是本文的主题,但了解 `int` `float` 对象之间的一些区别是很重要的。特别是,`int` 对象可以精确地表示整数,没有任何近似处理或大小限制。另一方面,`float` 对象可以表示很大范围内的小数,但并不是所有的数字都能被精确表示,它有最小值和最大值之分。因此,`float` 值应被视为真实值的近似值,它们只能保证有限的精度,组合 `float` 值可能会导致近似误差;如果不进行近似处理,下面两个表达式的计算结果均为 7
**浮点数**:“float” 得名于许多语言的小数点“浮动”表示。本书不深入底层表示,但要记住 `int` `float` 的差别:`int` 可以**精确**表示任意大的整数;`float` 覆盖范围更广但是近似值,有上下界。多个 `float` 运算会累积误差;理论上都等于 7 的表达式可能产生不同近似

> 译者注:不同于其他的编程语言,Python3 中的 `int` 值是无界的,也就是说它可以存储任意大小的数,具体可以查看 [PEP 237](https://peps.python.org/pep-0237/)。
> 译者注:不同于很多语言,Python 3 的 `int` 无上限,可存储任意大的整数,详见 [PEP 237](https://peps.python.org/pep-0237/)。

```py
>>> 7 / 3 * 3
Expand All @@ -53,7 +53,7 @@ Python 包含三种原始数字类型:整数(`int`)、浮点数(`float`
6.999999999999999
```

尽管上式是 `int` 值的组合,但一个 `int` 值除以另一个 `int` 值,却会得到一个 `float` 值:一个截断的有限近似值,相当于两个整数相除的实际比值
即便除法的两边都是 `int`,结果也会变成 `float`,得到真实比值的近似

```py
>>> type(1/3)
Expand All @@ -62,15 +62,15 @@ Python 包含三种原始数字类型:整数(`int`)、浮点数(`float`
0.3333333333333333
```

当我们使用等式测试时,这种近似就会出现问题
在做相等性比较时,这种近似就会显形

```py
>>> 1/3 == 0.333333333333333312345 # 请注意浮点数近似
>>> 1/3 == 0.333333333333333312345 # 注意浮点近似
True
```

`int` `float` 类之间的细微差别对编写程序有着广泛的影响,因此,这也是程序员必须熟记的细节。幸运的是,原始数据类型的数量很少,这无疑减少了精通一门编程语言所需的记忆量。此外,这些细节在许多编程语言中都是一致的,它们会由 [IEEE 754 浮点标准](http://en.wikipedia.org/wiki/IEEE_floating_point) 等社区指南强制要求执行
`int` `float` 的细微差异会影响程序正确性,务必牢记。好消息是原始类型很少,学习成本低;而且许多语言在这些细节上保持一致,例如遵循 [IEEE 754 浮点标准](http://en.wikipedia.org/wiki/IEEE_floating_point)。

非数值类型(Non-numeric types):值可以表示许多其他类型的数据,比如声音、图像、位置、网址、网络连接等等。它们中间的少数可以用原始数据类型表示,例如用于值 `True` 和 `False` 的 `bool` 类,其他大多数值的类型必须由程序员使用我们将在本章中学习到的组合和抽象方法来定义
**非数值类型**:值还可以代表声音、图像、地理位置、网址、网络连接等。部分可以直接用原始类型表示,如 `bool` 的 `True` 与 `False`;更多类型需要用本章将学到的“组合与抽象”自行定义

下面几节将介绍更多 Python 的原始数据类型,并将重点介绍它们在创建数据抽象方面所起到的作用。那些对更多细节感兴趣的人,可以查看在线书籍 Dive Into Python 3 中的一个关于 [原始数据类型](http://getpython3.com/diveintopython3/native-datatypes.html) 的章节,它给出了所有 Python 的原始数据类型以及如何操作它们的实用概述,包括大量的使用示例和实用技巧
接下来的小节会继续介绍 Python 的其他原始类型,并说明它们如何支撑数据抽象。想了解更全面的行为与示例,可参阅《Dive Into Python 3》的 [原始数据类型](http://getpython3.com/diveintopython3/native-datatypes.html) 章节
36 changes: 17 additions & 19 deletions sicp/2/2.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,23 @@
对应:无
:::

当我们希望在程序中表示世界上广泛的事物时,会发现它们中的大多数都具有复合结构。比如地理位置具有经纬度坐标。为了表示位置,我们希望我们的编程语言能够经度和纬度耦合在一起形成一对复合数据,使它能够作为单个概念单元被程序操作,同时也能作为可以单独考虑的两个部分
在程序中表示现实世界的各种事物时,绝大多数都具备复合结构。比如地理位置由经纬度组成。我们希望语言能把经度和纬度绑定成一对复合数据,既能当作单个概念操作,也能按两个部分分别处理

使用复合数据可以使程序更加模块化。如果我们能够将地理位置作为整体值进行操作,那么我们就可以将计算位置的程序部分与位置如何表示的细节隔离开来,这种将“数据表示”与“数据处理”的程序隔离的通用技术是一种强大的设计方法,称为数据抽象。数据抽象会使程序更易于设计、维护和修改
复合数据让程序更模块化。如果能把位置当作整体值来操作,就能把计算位置的逻辑与位置的表示方式隔离开来。这种把“数据表示”与“数据处理”分离的设计叫做**数据抽象**,能显著提升程序的可设计性、可维护性与可修改性

数据抽象与函数抽象类似。当我们创建一个函数抽象时,函数实现的细节可以被隐藏,而特定的函数本身可以被替换为具有相同整体行为的任何其他函数。换句话说,我们可以创建一个抽象来将函数的使用方式与实现细节分离。类似地,数据抽象可以将复合数据值的使用方式与其构造细节隔离开来
数据抽象与函数抽象同理。定义函数抽象时可以隐藏实现细节,任何具备相同行为的实现都能替换;也就是说,使用函数与函数的内部实现被解耦。类似地,数据抽象把复合数据的使用方式与其构造细节隔离

数据抽象的基本思想是构建程序,以便它们对抽象数据进行操作。也就是说,我们的程序应该以尽可能少的假设来使用数据,同时要将具体的数据表示定义为程序的独立部分。

程序的“操作抽象数据”和“定义具体表示”两个部分,会由一组根据具体表示来实现抽象数据的函数相连。为了说明该技术,我们将思考如何设计一组用于操作有理数的函数。
核心思想是让程序操作抽象数据,对具体表示做最少假设,同时把数据的具体表示定义在独立部分。程序中“操作抽象数据”与“定义具体表示”两部分由一组函数衔接,这些函数根据具体表示实现抽象数据。为展示这一点,我们来设计一组操作有理数的函数。

## 2.2.1 示例:有理数

有理数是整数的比值,并且有理数是实数的一个重要子类。 `1/3` 或 `17/29` 等有理数通常写为
有理数是整数的比值,是实数的重要子集。`1/3` 或 `17/29` 一般写成

```py
<分子>/<分母>
```

其中 `<分子>` `<分母>` 都是整数值的占位符,这两个部分能够准确表示有理数的值。实际上的整数除法会产生 `float` 近似值,失去整数的精确精度
其中 `<分子>` `<分母>` 都是整数占位符,这两个部分能精确表示一个有理数的值。直接做整数除法会产生 `float` 近似,失去整数的精度

```py
>>> 1/3
Expand All @@ -35,15 +33,15 @@
True
```

但是,我们可以通过将分子和分母组合在一起来创建有理数的精确表示
我们可以把分子和分母组合起来,得到有理数的精确表示

通过使用函数抽象,我们可以在实现程序的某些部分之前开始高效地编程。我们首先假设已经存在了一个从分子和分母构造有理数的方法,再假设有方法得到一个给定有理数的分子和分母。进一步假设得到以下三个函数
利用函数抽象,我们可以在未实现细节前先搭好程序骨架。先假设已有从分子分母构造有理数的方法,也能分别取出它们。设想有这三个函数

- `rational(n, d)` 返回分子为 `n`、分母为 `d` 的有理数
- `numer(x)` 返回有理数 `x` 的分子
- `denom(x)` 返回有理数 `x` 的分母

我们在这里使用了一个强大的程序设计策略:一厢情愿(wishful thinking)。即使我们还没有想好有理数是如何表示的,或者函数 `numer`、`denom``rational` 应该如何实现。但是如果我们确实定义了这三个函数,我们就可以进行加法、乘法、打印和测试有理数是否相等
这里用到一个强大的策略:**一厢情愿(wishful thinking)**。即便还不知道有理数的表示或 `numer`、`denom``rational` 的实现,只要假设它们存在,就能先写出加法、乘法、打印以及相等性判断

```py
>>> def add_rationals(x, y):
Expand All @@ -61,18 +59,18 @@ True
return numer(x) * denom(y) == numer(y) * denom(x)
```

现在我们有了选择器函数 `numer``denom` 以及构造函数 `rational` 定义的有理数运算,但还没有定义这些函数。我们需要某种方法将分子和分母粘合在一起形成一个复合值
现在有了基于 `numer``denom``rational` 的运算,但这些函数还没实现。我们需要一种方式把分子和分母粘合成复合值

## 2.2.2 对

为了使我们能够实现具体的数据抽象,Python 提供了一个名为 `list` 列表的复合结构,可以通过将表达式放在以逗号分隔的方括号内来构造。这样的表达式称为列表字面量
为了实现具体的数据抽象,Python 提供了复合结构 `list`,可通过逗号分隔的方括号构造,称为列表字面量

```py
>>> [10, 20]
[10, 20]
```

可以通过两种方式访问 ​​ 列表元素。第一种方法是通过我们熟悉的多重赋值方法,它将列表解构为单个元素并将每个元素与不同的名称绑定
访问列表元素有两种方式。其一是多重赋值,把列表解构成单个元素并分别绑定名称

```py
>>> pair = [10, 20]
Expand All @@ -85,7 +83,7 @@ True
20
```

访问列表中元素的第二种方法是通过元素选择运算符,也使用方括号表示。与列表字面量不同,直接跟在另一个表达式之后的方括号表达式不会计算为 `list` 值,而是从前面表达式的值中选择一个元素
第二种是元素选择运算符,也用方括号。跟在表达式后的方括号不会生成列表,而是从前面表达式的值里选出某个元素

```py
>>> pair[0]
Expand All @@ -94,9 +92,9 @@ True
20
```

Python 中的列表(以及大多数其他编程语言中的序列)是从 0 开始索引的,这意味着索引 0 选择第一个元素,索引 1 选择第二个元素,以此类推。对于这种索引约定的一种直觉是,索引表示元素距列表开头的偏移量
Python(以及多数语言)的序列从 0 开始索引:0 是第一个元素,1 是第二个,以此类推。直觉上,索引表示元素距开头的偏移

元素选择运算符的等效函数称为 `getitem` ,它也使用 0 索引位置从列表中选择元素
等价的函数是 `getitem`,同样用 0 索引选择元素

```py
>>> from operator import getitem
Expand All @@ -106,9 +104,9 @@ Python 中的列表(以及大多数其他编程语言中的序列)是从 0
20
```

双元素列表并不是 Python 中表示对的唯一方法。将两个值捆绑在一起成为一个值的任何方式都可以被认为是一对。列表是一种常用的方法,它也可以包含两个以上的元素,我们将在本章后面进行探讨
双元素列表不是唯一的“对”表示。只要能把两个值捆成一个值,都可视作一对。列表是常用方式,也能容纳多于两个元素,本章稍后会继续介绍

代表有理数:我们现在可以将有理数表示为两个整数的对:一个分子和一个分母
表示有理数:现在可以把有理数表示成两个整数的对:分子与分母

```py
>>> def rational(n, d):
Expand Down
Loading
Loading