SICP 第一章 使用函数抽象概念 1.2 编程元素

文档:1.2 Elements of Programming
参考:cs61a.org/spring2018


1.2 编程元素

编程语言不仅仅是操作计算机执行任务的一种手段。 该语言也是我们组织关于计算过程的想法的框架。 程序用于在编程社区的成员之间传达这些想法。 因此,程序必须易读,并且只是附带在机器上执行。

当我们描述一种语言时,我们应该特别注意语言为简单的想法组合起来形成更复杂的想法所提供的手段。 每个强大的语言都有三种这样的机制:

  • 基本的表达式和语句,它们代表该最简单的构建代码块
  • 组合方式,复合元素由较简单的元素构成
  • 抽象的手段,复合元素可以用它命名和操纵。

在编程中,我们处理两种元素:函数和数据。 (很快我们会发现它们并不非常不同。)非正式地,数据是我们要操纵的东西,函数描述了操纵数据的规则。 因此,任何强大的编程语言都应该能够描述基本数据和基本函数,以及具有组合和抽象功能和数据的一些方法。

1.2.1 表达式

在上一节中实验过Python解释器后,我们现在重新开始,按顺序一步一步探索Python语言。 如果例子对您似乎太简单的话,请您耐心等待 - 更激动人心的还在后面。

我们从基本表达式开始。 基本表达式的其中一种是数值。 更准确地说,是由十进制数字表示的数值组成的表达式。

>>> 42 
42

表示数值的表达式可以与数学运算符相结合,形成一个复合表达式,解释器将求出它的值:

>>> -1 - -1
0
>>> 1/2 + 1/4 + 1/8 + 1/16 + 1/32 + 1/64 + 1/128
0.9921875

这些算数表达式使用中缀符号,其中操作运算符(例如,+, - ,*或/)出现在操作数(数值)之间。 Python中有许多形成复合表达式的方式。 我们现在不会立即枚举它们,而将在课程中引入新的表达式,以及它们支持的语言特征。

1.2.2 调用表达式

最重要的复合表达式是调用表达式,它将一个函数应用于一些参数。 从代数的方向思考,一个函数的数学概念是从一些输入到输出值的映射。 例如,max函数将其输入映射到单个输出,输出是输入中最大的值。 Python表示函数的方式与传统数学中的相同。

>>> max(7.5, 9.5)
9.5

运算符指定一个函数。 当对这个调用表达式进行求值时,我们说使用参数7.5和9.5来调用函数max,并返回9.5。

调用表达式中的参数的顺序很重要。 例如,函数pow是计算第一个参数的第二个参数次方。

>>> pow(100, 2)
10000
>>>pow(2,100)
1267650600228229401496703205376

函数符号相对于中缀符号的数学惯例有三个主要优点。 首先,函数可以采用任意数量的参数:

>>> max(1, -2, 3, -4)
3

由于函数名称始终位于其参数之前,因此不会出现歧义。

其次,函数符号以直接的方式扩展为嵌套表达式,其中元素本身是复合表达式。 在嵌套调用表达式中,与复合中缀表达式不同,嵌套的结构在括号中完全显式。

>>> max(min(1, -2), min(pow(3, 5), -4))
-2

(原则上)这种嵌套的深度没有限制,并且Python解释器可以解释任何复杂的表达式。 然而,人们很快就被多层次的嵌套搞晕了。 您作为程序员的重要作用是构建表达式,以便您自己,编程合作伙伴以及将来可能会阅读您代码的其他人员仍然可以解释这些表达式。

最后,数学符号有多种形式:乘法出现在术语之间,指数出现在上标,除法为横杠,平方根作为具有侧壁的屋顶。 这种符号中有一些很难输入! 然而,所有这些复杂性可以通过调用表达式的符号来统一。 虽然Python通过中缀符号(如+和 - )支持常用数学运算符,但任何运算符都可以表示为带有名称的函数。

1.2.3 导入库函数

Python定义了非常大量的函数,包括上一节中提到的操作符函数,但默认情况下不会使用它们的名字。 相反,它将已知的函数和其他东西组织成模块,它们组合在一起构成Python库。 要使用这些元素时,可以导入它们。 例如,math模块提供了各种熟悉的数学函数:

>>> from math import sqrt, exp
>>> sqrt(256)
16.0
```
operator 模块提供了中缀运算符对应的函数:

from operator import add, sub, mul
add(14, 28)
42
sub(100, mul(7, add(8, 4)))
16

`import`语句指定模块名称(例如,`operator`或`math`),然后列出要导入的模块的命名属性(例如,sqrt)。 一旦导入功能,就可以多次调用。

使用这些运算符函数(例如,add)和运算符符号本身(例如,+)之间没有区别。 通常,大多数程序员使用符号和中缀符号表示简单的算术。

Python 3 Library Docs列出了每个模块定义的函数,如数学模块。 但是,这个文档是为那些熟悉整个语言的开发人员编写的。 现在,您可能会发现使用函数进行实践会比阅读文档更多地了解其行为。 当您熟悉Python语言和词汇时,本文档将成为一份很有价值的参考资料。
###1.2.4 名称和环境
编程语言的关键之一是使用名称来引用计算对象。 如果一个值被赋予一个名字,我们会说这个名字绑定到该值。

在Python中,我们可以使用赋值语句建立新的绑定,该语句包含一个左边的名字,右边是一个值:

radius = 10
radius
10
2 * radius
20

名称也可以通过` import `语句绑定:

from math import pi
pi * 71 / 223
1.0002380197528042

`=`符号在Python中被称为赋值运算符(和许多其他语言一样)。 赋值是我们最简单的抽象方法,因为它允许我们使用简单的名称来指代复合操作的结果,例如上面计算的`area`面积。 这样一来,我们可以通过逐步建立复杂程度越来越高的计算对象来构建复杂的程序。

将名称绑定到值上并稍后通过名称来检索这些值的可能,意味着解释器必须保留某种内存来跟踪名称和值的绑定。 这个内存被称为环境。

名称也可以绑定到函数上。 例如,名称`max`绑定到我们使用的`max`最大函数。 函数与数值不同,以文本形式呈现很难,因此Python会在描述函数时打印标识描述:

max
<built-in function max>

我们可以使用赋值运算符来给现有函数起新的名字:

f = max
f
<built-in function max>
f(2, 3, 4)
4

成功的赋值语句可以将名称绑定到新的值:

f = 2
f
2

在Python中,名称通常被称为变量名称或变量,因为它们可以在执行程序的过程中被绑定到不同的值。 当通过赋值将名称绑定到新值时,它不再绑定到任何先前的值。 人们甚至可以将内置名称绑定到新值。

max = 5
max
5

分配`max`最大值为5后,名称`max`不再绑定到一个函数,因此尝试调用`max(2,3,4)`会导致错误。

执行赋值语句时,Python将更改绑定到左侧的名称之前,将右侧的表达式进行计算。 因此,即使是由赋值语句绑定的名称,也可以引用右侧表达式中的名称。

x = 2
x = x + 1
x
3

我们还可以在单个语句中为多个名称分配多个值,其中`=`左边的名称和`=`右侧的表达式用逗号分隔。

area, circumference = pi * radius * radius, 2 * pi * radius
area
314.1592653589793
circumference
62.83185307179586

更改一个名称的值不会影响其他名称。 下面,即使名称`area`面积被限定在最初根据`radius`半径定义的值,`area`的值也没有变化。 更新`area`的值需要另一个赋值语句。

radius = 11
area
314.1592653589793
area = pi * radius * radius
380.132711084365

使用多个赋值时,在`=`左侧的任何名称都绑定到这些值之前,解释器将计算`=`右侧的所有表达式。 作为此规则的结果,交换绑定到两个名称的值可以在单个语句中执行。

x, y = 3, 4.5
y, x = x, y
x
4.5
y
3

###1.2.5 嵌套表达式的求解
本章中我们的目标之一就是隔离程序化思考的问题。 就一个例子而言,在求解嵌套的调用表达式时,解释器本身会遵循一个过程。

要求解一个调用表达式,Python将执行以下操作:

1.求解运算符和操作数子表达式
2.在值为操作数子表达式的参数上调用值为运算符子表达式的函数。

即使是这个简单的过程也能说明过程的一些重点。 第一步要求为了完成调用表达式的求值过程,我们首先需要求出其他表达式。 因此,求值过程本质上是递归的; 也就是说,作为其中一个步骤它会调用其自身。

sub(pow(2, add(1, 10)), pow(2, 5))
2016

这个例子需要求值四次。 如果我们每个需要求值的表达式提取出来,我们可以可视化该过程的层次结构。

![引用自cs61a讲义](http://upload-images.jianshu.io/upload_images/5899832-06a93da78d94661a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
这个例子叫做表达式树。在计算机科学中,树都是从顶端往下生长。树中每个点的对象称为节点;在这种情况下,它们是表达式以及它们的值。

想要求出根节点,也就是顶部的完整表达式,需要首先求出其子表达式。叶子表达式(即,没有子节点的节点)表示函数或数值。内部节点有两个部分:应用的求值规则的调用表达式以及该表达式的结果。观察这棵树的求值,我们可以想象,操作数的值向上传递,从终端节点开始,然后在更高的层次上进行组合。

接下来,每一步的重复应用将会把我们带到需要评估的点,这里不是调用表达式,而是基础表达式,诸如数字(例如`2`)和名称(例如`add`)之类的。我们通过以下规定来处理基本案件:
1.数字求值为它标明的数值
2.名称求值为当前环境中这个名称所关联的值

要注意环境的重要作用是决定表达式中符号的含义。
`>>> add(x, 1)`

在python中,在不指定任何关于环境的信息,提供名称`x`(以及`add`)的含义的情况下,求解这样的表达式的值是没有意义的。 环境提供了求值过程发生的背景,这对我们理解程序执行起到了重要的作用。

此求值过程不符合所有Python代码的求解,它仅仅是调用表达式、数字和名称。 例如,它并不能处理赋值语句。
`>>> x = 3`

上述代码的执行不返回值,也不求解任何参数上的函数,因为赋值的目的是将名称绑定到值上。 一般来说,语句不会被求解但是被执行; 它们不产生值,但是会带来一些改变。 每种表达式或语句都有自己的求值或执行过程。

当我们说“数字求值为数值”时,我们的实际意思是Python解释器将数字求解为数值。 Python的解释器赋予了编程语言意义。 假设解释器是一个固定程序,我们可以说数字(和表达式)本身可以在Python程序的上下文中求值。

###1.2.6 函数图解
在本文中,我们将区分两种类型的函数。

**纯函数**。 函数有一些输入(参数)并返回一些输出(调用结果的函数)。内建函数

abs(-2)

2

可以描述为一个接受输入并产生输出的小型机器。

![](http://upload-images.jianshu.io/upload_images/5899832-182fc34d62103abd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
函数`abs`是纯函数。 纯函数具有某种特性,调用它们除了返回值之外没有任何效果。 而且,当使用相同的参数调用两次时,纯函数必须始终返回相同的值。
**非纯函数**。 除了返回一个值之外,调用非纯函数可能会产生副作用,这会对解释器或计算机的状态进行一些更改。 常见的副作用之一是生成超出返回值的额外输出,比如调用`print`函数。

print(1, 2, 3)
1 2 3

虽然在例子中`print`和`abs`似乎是相似的,但它们的工作方式完全不同。`print`返回的值始终为`None`,它是一个Python特殊值,表示没有任何东西。Python交互式解释器并不会自动打印`None`值。 `print`本身是打印了输出,作为调用中的副作用。
![](http://upload-images.jianshu.io/upload_images/5899832-1b630867f1a9745c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
调用`print`的嵌套表达式会突出凸显了它的非纯字符。

print(print(1), print(2))
1
2
None None

如果您发现此输出是预料之外的,可以绘制表达式树,以弄清为什么此表达式的求值会产生奇怪的输出。

要小心`print`! 它的返回值为`None`,意味着它不应该在赋值语句中用作表达式。

two = print(2)
2
print(two)
None

纯函数受到限制,因为它们不会在一段时间内产生副作用或改变行为。加上这些限制可以带来巨大的好处。首先,纯函数可以更可靠地组合成复合调用表达式。我们可以在上面的非纯函数示例中看到,当在操作数表达式中使用时,`print`不会返回有用的结果。另一方面,我们可以在嵌套表达式中有效地使用`max`,`pow`和`sqrt`等函数。

第二,纯函数往往更容易测试。参数列表将始终得出相同的返回值,可以将其与预期的返回值进行比较。本章后面将更详细地讨论如何测试。

第三,第4章将说明纯函数对编写并行程序至关重要,其中可以同时对多个调用表达式进行求解。

相比之下,第2章研究了一系列非纯粹的功能并描述了它们的用途。

由于这些原因,我们在本章剩下的部分将重点关注创建和使用纯函数。`print`功能仅用于我们可以看到计算的中间结果。







上一节:[SICP 第一章 使用函数抽象概念 1.1 引言](http://www.jianshu.com/p/dc0fcb305aba)
下一节:[SICP 第一章 使用函数抽象概念 1.3 定义新函数](http://www.jianshu.com/p/3595e4254ab0)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,475评论 4 372
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,744评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,101评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,732评论 0 221
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,141评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,049评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,188评论 2 320
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,965评论 0 213
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,716评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,867评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,341评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,663评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,376评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,200评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,990评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,179评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,979评论 2 279

推荐阅读更多精彩内容