1.2 編程元素
編程語言不僅僅是操作計(jì)算機(jī)執(zhí)行任務(wù)的一種手段。 該語言也是我們組織關(guān)于計(jì)算過程的想法的框架。 程序用于在編程社區(qū)的成員之間傳達(dá)這些想法。 因此萎河,程序必須易讀,并且只是附帶在機(jī)器上執(zhí)行。
當(dāng)我們描述一種語言時(shí)尽楔,我們應(yīng)該特別注意語言為簡單的想法組合起來形成更復(fù)雜的想法所提供的手段。 每個(gè)強(qiáng)大的語言都有三種這樣的機(jī)制:
- 基本的表達(dá)式和語句第练,它們代表該最簡單的構(gòu)建代碼塊
- 組合方式阔馋,復(fù)合元素由較簡單的元素構(gòu)成
- 抽象的手段,復(fù)合元素可以用它命名和操縱娇掏。
在編程中呕寝,我們處理兩種元素:函數(shù)和數(shù)據(jù)。 (很快我們會(huì)發(fā)現(xiàn)它們并不非常不同婴梧。)非正式地下梢,數(shù)據(jù)是我們要操縱的東西,函數(shù)描述了操縱數(shù)據(jù)的規(guī)則塞蹭。 因此孽江,任何強(qiáng)大的編程語言都應(yīng)該能夠描述基本數(shù)據(jù)和基本函數(shù),以及具有組合和抽象功能和數(shù)據(jù)的一些方法番电。
1.2.1 表達(dá)式
在上一節(jié)中實(shí)驗(yàn)過Python解釋器后岗屏,我們現(xiàn)在重新開始,按順序一步一步探索Python語言漱办。 如果例子對(duì)您似乎太簡單的話这刷,請(qǐng)您耐心等待 - 更激動(dòng)人心的還在后面。
我們從基本表達(dá)式開始洼冻。 基本表達(dá)式的其中一種是數(shù)值崭歧。 更準(zhǔn)確地說,是由十進(jìn)制數(shù)字表示的數(shù)值組成的表達(dá)式撞牢。
>>> 42
42
表示數(shù)值的表達(dá)式可以與數(shù)學(xué)運(yùn)算符相結(jié)合率碾,形成一個(gè)復(fù)合表達(dá)式叔营,解釋器將求出它的值:
>>> -1 - -1
0
>>> 1/2 + 1/4 + 1/8 + 1/16 + 1/32 + 1/64 + 1/128
0.9921875
這些算數(shù)表達(dá)式使用中綴符號(hào),其中操作運(yùn)算符(例如所宰,+绒尊, - ,*或/)出現(xiàn)在操作數(shù)(數(shù)值)之間仔粥。 Python中有許多形成復(fù)合表達(dá)式的方式婴谱。 我們現(xiàn)在不會(huì)立即枚舉它們,而將在課程中引入新的表達(dá)式躯泰,以及它們支持的語言特征谭羔。
1.2.2 調(diào)用表達(dá)式
最重要的復(fù)合表達(dá)式是調(diào)用表達(dá)式,它將一個(gè)函數(shù)應(yīng)用于一些參數(shù)麦向。 從代數(shù)的方向思考瘟裸,一個(gè)函數(shù)的數(shù)學(xué)概念是從一些輸入到輸出值的映射。 例如诵竭,max
函數(shù)將其輸入映射到單個(gè)輸出话告,輸出是輸入中最大的值。 Python表示函數(shù)的方式與傳統(tǒng)數(shù)學(xué)中的相同卵慰。
>>> max(7.5, 9.5)
9.5
運(yùn)算符指定一個(gè)函數(shù)沙郭。 當(dāng)對(duì)這個(gè)調(diào)用表達(dá)式進(jìn)行求值時(shí),我們說使用參數(shù)7.5和9.5來調(diào)用函數(shù)max
裳朋,并返回9.5病线。
調(diào)用表達(dá)式中的參數(shù)的順序很重要。 例如再扭,函數(shù)pow
是計(jì)算第一個(gè)參數(shù)的第二個(gè)參數(shù)次方氧苍。
>>> pow(100, 2)
10000
>>>pow(2,100)
1267650600228229401496703205376
函數(shù)符號(hào)相對(duì)于中綴符號(hào)的數(shù)學(xué)慣例有三個(gè)主要優(yōu)點(diǎn)。 首先泛范,函數(shù)可以采用任意數(shù)量的參數(shù):
>>> max(1, -2, 3, -4)
3
由于函數(shù)名稱始終位于其參數(shù)之前让虐,因此不會(huì)出現(xiàn)歧義。
其次罢荡,函數(shù)符號(hào)以直接的方式擴(kuò)展為嵌套表達(dá)式赡突,其中元素本身是復(fù)合表達(dá)式。 在嵌套調(diào)用表達(dá)式中区赵,與復(fù)合中綴表達(dá)式不同惭缰,嵌套的結(jié)構(gòu)在括號(hào)中完全顯式。
>>> max(min(1, -2), min(pow(3, 5), -4))
-2
(原則上)這種嵌套的深度沒有限制笼才,并且Python解釋器可以解釋任何復(fù)雜的表達(dá)式漱受。 然而,人們很快就被多層次的嵌套搞暈了骡送。 您作為程序員的重要作用是構(gòu)建表達(dá)式昂羡,以便您自己絮记,編程合作伙伴以及將來可能會(huì)閱讀您代碼的其他人員仍然可以解釋這些表達(dá)式。
最后虐先,數(shù)學(xué)符號(hào)有多種形式:乘法出現(xiàn)在術(shù)語之間怨愤,指數(shù)出現(xiàn)在上標(biāo),除法為橫杠蛹批,平方根作為具有側(cè)壁的屋頂撰洗。 這種符號(hào)中有一些很難輸入! 然而腐芍,所有這些復(fù)雜性可以通過調(diào)用表達(dá)式的符號(hào)來統(tǒng)一差导。 雖然Python通過中綴符號(hào)(如+和 - )支持常用數(shù)學(xué)運(yùn)算符,但任何運(yùn)算符都可以表示為帶有名稱的函數(shù)甸赃。
1.2.3 導(dǎo)入庫函數(shù)
Python定義了非常大量的函數(shù)柿汛,包括上一節(jié)中提到的操作符函數(shù),但默認(rèn)情況下不會(huì)使用它們的名字埠对。 相反,它將已知的函數(shù)和其他東西組織成模塊裁替,它們組合在一起構(gòu)成Python庫项玛。 要使用這些元素時(shí),可以導(dǎo)入它們弱判。 例如襟沮,math
模塊提供了各種熟悉的數(shù)學(xué)函數(shù):
>>> from math import sqrt, exp
>>> sqrt(256)
16.0
```
operator 模塊提供了中綴運(yùn)算符對(duì)應(yīng)的函數(shù):
from operator import add, sub, mul
add(14, 28)
42
sub(100, mul(7, add(8, 4)))
16
`import`語句指定模塊名稱(例如义起,`operator`或`math`)馒铃,然后列出要導(dǎo)入的模塊的命名屬性(例如,sqrt)冲茸。 一旦導(dǎo)入功能遭商,就可以多次調(diào)用固灵。
使用這些運(yùn)算符函數(shù)(例如,add)和運(yùn)算符符號(hào)本身(例如劫流,+)之間沒有區(qū)別巫玻。 通常,大多數(shù)程序員使用符號(hào)和中綴符號(hào)表示簡單的算術(shù)祠汇。
Python 3 Library Docs列出了每個(gè)模塊定義的函數(shù)仍秤,如數(shù)學(xué)模塊。 但是可很,這個(gè)文檔是為那些熟悉整個(gè)語言的開發(fā)人員編寫的诗力。 現(xiàn)在,您可能會(huì)發(fā)現(xiàn)使用函數(shù)進(jìn)行實(shí)踐會(huì)比閱讀文檔更多地了解其行為我抠。 當(dāng)您熟悉Python語言和詞匯時(shí)苇本,本文檔將成為一份很有價(jià)值的參考資料导坟。
###1.2.4 名稱和環(huán)境
編程語言的關(guān)鍵之一是使用名稱來引用計(jì)算對(duì)象。 如果一個(gè)值被賦予一個(gè)名字圈澈,我們會(huì)說這個(gè)名字綁定到該值惫周。
在Python中,我們可以使用賦值語句建立新的綁定康栈,該語句包含一個(gè)左邊的名字递递,右邊是一個(gè)值:
radius = 10
radius
10
2 * radius
20
名稱也可以通過` import `語句綁定:
from math import pi
pi * 71 / 223
1.0002380197528042
`=`符號(hào)在Python中被稱為賦值運(yùn)算符(和許多其他語言一樣)。 賦值是我們最簡單的抽象方法啥么,因?yàn)樗试S我們使用簡單的名稱來指代復(fù)合操作的結(jié)果登舞,例如上面計(jì)算的`area`面積。 這樣一來悬荣,我們可以通過逐步建立復(fù)雜程度越來越高的計(jì)算對(duì)象來構(gòu)建復(fù)雜的程序菠秒。
將名稱綁定到值上并稍后通過名稱來檢索這些值的可能,意味著解釋器必須保留某種內(nèi)存來跟蹤名稱和值的綁定氯迂。 這個(gè)內(nèi)存被稱為環(huán)境践叠。
名稱也可以綁定到函數(shù)上。 例如嚼蚀,名稱`max`綁定到我們使用的`max`最大函數(shù)禁灼。 函數(shù)與數(shù)值不同,以文本形式呈現(xiàn)很難轿曙,因此Python會(huì)在描述函數(shù)時(shí)打印標(biāo)識(shí)描述:
max
<built-in function max>
我們可以使用賦值運(yùn)算符來給現(xiàn)有函數(shù)起新的名字:
f = max
f
<built-in function max>
f(2, 3, 4)
4
成功的賦值語句可以將名稱綁定到新的值:
f = 2
f
2
在Python中弄捕,名稱通常被稱為變量名稱或變量,因?yàn)樗鼈兛梢栽趫?zhí)行程序的過程中被綁定到不同的值导帝。 當(dāng)通過賦值將名稱綁定到新值時(shí)守谓,它不再綁定到任何先前的值。 人們甚至可以將內(nèi)置名稱綁定到新值您单。
max = 5
max
5
分配`max`最大值為5后斋荞,名稱`max`不再綁定到一個(gè)函數(shù),因此嘗試調(diào)用`max(2,3,4)`會(huì)導(dǎo)致錯(cuò)誤睹限。
執(zhí)行賦值語句時(shí)譬猫,Python將更改綁定到左側(cè)的名稱之前,將右側(cè)的表達(dá)式進(jìn)行計(jì)算羡疗。 因此染服,即使是由賦值語句綁定的名稱,也可以引用右側(cè)表達(dá)式中的名稱叨恨。
x = 2
x = x + 1
x
3
我們還可以在單個(gè)語句中為多個(gè)名稱分配多個(gè)值柳刮,其中`=`左邊的名稱和`=`右側(cè)的表達(dá)式用逗號(hào)分隔。
area, circumference = pi * radius * radius, 2 * pi * radius
area
314.1592653589793
circumference
62.83185307179586
更改一個(gè)名稱的值不會(huì)影響其他名稱。 下面秉颗,即使名稱`area`面積被限定在最初根據(jù)`radius`半徑定義的值痢毒,`area`的值也沒有變化。 更新`area`的值需要另一個(gè)賦值語句蚕甥。
radius = 11
area
314.1592653589793
area = pi * radius * radius
380.132711084365
使用多個(gè)賦值時(shí)哪替,在`=`左側(cè)的任何名稱都綁定到這些值之前,解釋器將計(jì)算`=`右側(cè)的所有表達(dá)式菇怀。 作為此規(guī)則的結(jié)果凭舶,交換綁定到兩個(gè)名稱的值可以在單個(gè)語句中執(zhí)行。
x, y = 3, 4.5
y, x = x, y
x
4.5
y
3
###1.2.5 嵌套表達(dá)式的求解
本章中我們的目標(biāo)之一就是隔離程序化思考的問題爱沟。 就一個(gè)例子而言帅霜,在求解嵌套的調(diào)用表達(dá)式時(shí),解釋器本身會(huì)遵循一個(gè)過程呼伸。
要求解一個(gè)調(diào)用表達(dá)式身冀,Python將執(zhí)行以下操作:
1.求解運(yùn)算符和操作數(shù)子表達(dá)式
2.在值為操作數(shù)子表達(dá)式的參數(shù)上調(diào)用值為運(yùn)算符子表達(dá)式的函數(shù)。
即使是這個(gè)簡單的過程也能說明過程的一些重點(diǎn)括享。 第一步要求為了完成調(diào)用表達(dá)式的求值過程搂根,我們首先需要求出其他表達(dá)式。 因此奶浦,求值過程本質(zhì)上是遞歸的; 也就是說兄墅,作為其中一個(gè)步驟它會(huì)調(diào)用其自身。
sub(pow(2, add(1, 10)), pow(2, 5))
2016
這個(gè)例子需要求值四次澳叉。 如果我們每個(gè)需要求值的表達(dá)式提取出來,我們可以可視化該過程的層次結(jié)構(gòu)沐悦。
![引用自cs61a講義](http://upload-images.jianshu.io/upload_images/5899832-06a93da78d94661a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
這個(gè)例子叫做表達(dá)式樹成洗。在計(jì)算機(jī)科學(xué)中,樹都是從頂端往下生長藏否。樹中每個(gè)點(diǎn)的對(duì)象稱為節(jié)點(diǎn);在這種情況下瓶殃,它們是表達(dá)式以及它們的值。
想要求出根節(jié)點(diǎn)副签,也就是頂部的完整表達(dá)式遥椿,需要首先求出其子表達(dá)式。葉子表達(dá)式(即淆储,沒有子節(jié)點(diǎn)的節(jié)點(diǎn))表示函數(shù)或數(shù)值冠场。內(nèi)部節(jié)點(diǎn)有兩個(gè)部分:應(yīng)用的求值規(guī)則的調(diào)用表達(dá)式以及該表達(dá)式的結(jié)果。觀察這棵樹的求值本砰,我們可以想象碴裙,操作數(shù)的值向上傳遞,從終端節(jié)點(diǎn)開始,然后在更高的層次上進(jìn)行組合舔株。
接下來莺琳,每一步的重復(fù)應(yīng)用將會(huì)把我們帶到需要評(píng)估的點(diǎn),這里不是調(diào)用表達(dá)式载慈,而是基礎(chǔ)表達(dá)式惭等,諸如數(shù)字(例如`2`)和名稱(例如`add`)之類的。我們通過以下規(guī)定來處理基本案件:
1.數(shù)字求值為它標(biāo)明的數(shù)值
2.名稱求值為當(dāng)前環(huán)境中這個(gè)名稱所關(guān)聯(lián)的值
要注意環(huán)境的重要作用是決定表達(dá)式中符號(hào)的含義办铡。
`>>> add(x, 1)`
在python中辞做,在不指定任何關(guān)于環(huán)境的信息,提供名稱`x`(以及`add`)的含義的情況下料扰,求解這樣的表達(dá)式的值是沒有意義的凭豪。 環(huán)境提供了求值過程發(fā)生的背景,這對(duì)我們理解程序執(zhí)行起到了重要的作用晒杈。
此求值過程不符合所有Python代碼的求解嫂伞,它僅僅是調(diào)用表達(dá)式、數(shù)字和名稱拯钻。 例如帖努,它并不能處理賦值語句。
`>>> x = 3`
上述代碼的執(zhí)行不返回值粪般,也不求解任何參數(shù)上的函數(shù)拼余,因?yàn)橘x值的目的是將名稱綁定到值上。 一般來說亩歹,語句不會(huì)被求解但是被執(zhí)行; 它們不產(chǎn)生值匙监,但是會(huì)帶來一些改變。 每種表達(dá)式或語句都有自己的求值或執(zhí)行過程小作。
當(dāng)我們說“數(shù)字求值為數(shù)值”時(shí)亭姥,我們的實(shí)際意思是Python解釋器將數(shù)字求解為數(shù)值。 Python的解釋器賦予了編程語言意義顾稀。 假設(shè)解釋器是一個(gè)固定程序达罗,我們可以說數(shù)字(和表達(dá)式)本身可以在Python程序的上下文中求值。
###1.2.6 函數(shù)圖解
在本文中静秆,我們將區(qū)分兩種類型的函數(shù)粮揉。
**純函數(shù)**。 函數(shù)有一些輸入(參數(shù))并返回一些輸出(調(diào)用結(jié)果的函數(shù))抚笔。內(nèi)建函數(shù)
abs(-2)
2
可以描述為一個(gè)接受輸入并產(chǎn)生輸出的小型機(jī)器扶认。
![](http://upload-images.jianshu.io/upload_images/5899832-182fc34d62103abd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
函數(shù)`abs`是純函數(shù)。 純函數(shù)具有某種特性塔沃,調(diào)用它們除了返回值之外沒有任何效果蝠引。 而且阳谍,當(dāng)使用相同的參數(shù)調(diào)用兩次時(shí),純函數(shù)必須始終返回相同的值螃概。
**非純函數(shù)**矫夯。 除了返回一個(gè)值之外,調(diào)用非純函數(shù)可能會(huì)產(chǎn)生副作用吊洼,這會(huì)對(duì)解釋器或計(jì)算機(jī)的狀態(tài)進(jìn)行一些更改训貌。 常見的副作用之一是生成超出返回值的額外輸出,比如調(diào)用`print`函數(shù)冒窍。
print(1, 2, 3)
1 2 3
雖然在例子中`print`和`abs`似乎是相似的递沪,但它們的工作方式完全不同。`print`返回的值始終為`None`综液,它是一個(gè)Python特殊值款慨,表示沒有任何東西。Python交互式解釋器并不會(huì)自動(dòng)打印`None`值谬莹。 `print`本身是打印了輸出,作為調(diào)用中的副作用檩奠。
![](http://upload-images.jianshu.io/upload_images/5899832-1b630867f1a9745c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
調(diào)用`print`的嵌套表達(dá)式會(huì)突出凸顯了它的非純字符。
print(print(1), print(2))
1
2
None None
如果您發(fā)現(xiàn)此輸出是預(yù)料之外的附帽,可以繪制表達(dá)式樹埠戳,以弄清為什么此表達(dá)式的求值會(huì)產(chǎn)生奇怪的輸出。
要小心`print`蕉扮! 它的返回值為`None`整胃,意味著它不應(yīng)該在賦值語句中用作表達(dá)式。
two = print(2)
2
print(two)
None
純函數(shù)受到限制喳钟,因?yàn)樗鼈儾粫?huì)在一段時(shí)間內(nèi)產(chǎn)生副作用或改變行為屁使。加上這些限制可以帶來巨大的好處。首先奔则,純函數(shù)可以更可靠地組合成復(fù)合調(diào)用表達(dá)式屋灌。我們可以在上面的非純函數(shù)示例中看到,當(dāng)在操作數(shù)表達(dá)式中使用時(shí)应狱,`print`不會(huì)返回有用的結(jié)果。另一方面祠丝,我們可以在嵌套表達(dá)式中有效地使用`max`疾呻,`pow`和`sqrt`等函數(shù)。
第二写半,純函數(shù)往往更容易測試岸蜗。參數(shù)列表將始終得出相同的返回值,可以將其與預(yù)期的返回值進(jìn)行比較叠蝇。本章后面將更詳細(xì)地討論如何測試璃岳。
第三,第4章將說明純函數(shù)對(duì)編寫并行程序至關(guān)重要,其中可以同時(shí)對(duì)多個(gè)調(diào)用表達(dá)式進(jìn)行求解铃慷。
相比之下单芜,第2章研究了一系列非純粹的功能并描述了它們的用途。
由于這些原因犁柜,我們?cè)诒菊率O碌牟糠謱⒅攸c(diǎn)關(guān)注創(chuàng)建和使用純函數(shù)洲鸠。`print`功能僅用于我們可以看到計(jì)算的中間結(jié)果。
上一節(jié):[SICP 第一章 使用函數(shù)抽象概念 1.1 引言](http://www.reibang.com/p/dc0fcb305aba)
下一節(jié):[SICP 第一章 使用函數(shù)抽象概念 1.3 定義新函數(shù)](http://www.reibang.com/p/3595e4254ab0)