SICP 第一章 使用函數(shù)抽象概念 1.3 定義新函數(shù)

文檔:1.3 Defining New Functions
參考:cs61a.org/spring2018


我們已經(jīng)在Python中確定了一些在任何強(qiáng)大的編程語言中都有的元素:

1.數(shù)字和算術(shù)運(yùn)算是基本的內(nèi)置數(shù)據(jù)值和函數(shù)。
2.嵌套函數(shù)提供了組合操作的方法。
3.將名稱綁定到值的方式提供了有限的抽象方法。

現(xiàn)在我們將了解函數(shù)定義,一個更強(qiáng)大的抽象技術(shù),通過該技術(shù)可以將名稱綁定到復(fù)合操作上,然后將其作為單元引用。

我們首先研究如何表達(dá)square平方的這個概念。 我們可能會說:“對數(shù)求平方就是將數(shù)自己乘上自己?!痹赑ython中的表達(dá)如下:

>>> def square(x):
        return mul(x, x)

它定義了一個賦予了名稱square的新函數(shù)。 這個用戶定義的函數(shù)并沒有內(nèi)置到解釋器中。 它代表著自己和自己相乘的復(fù)合操作。 這個定義中的x稱為形式參數(shù),它為被乘的東西提供一個名稱。 該定義創(chuàng)建了此用戶定義的函數(shù),并將其與名稱square相關(guān)聯(lián)。

如何定義一個函數(shù)。 函數(shù)定義包含一個def語句,該語句包含了<name>和一個帶有名字的<formal parameter>(形式參數(shù)),然后是一個稱為函數(shù)體的return返回語句,該語句指定了函數(shù)的<return expression>(返回表達(dá)式),這是一個每次函數(shù)調(diào)用都需要求值的表達(dá)式:

def <name>(<formal parameters>):
    return <return expression>

第二行必須縮進(jìn) - 按照慣例大多數(shù)程序員使用四個空格來縮進(jìn)。 返回表達(dá)式不會立即求值; 它被存儲為新定義函數(shù)的一部分,并且僅在函數(shù)最終被調(diào)用時被求解。

定義了square后,我們可以用表達(dá)式來調(diào)用它:

>>> square(21)
441
>>> square(add(2, 5))
49
>>> square(square(3))
81

我們還可以使用square作為構(gòu)建塊來定義其他功能。 例如,我們可以輕松定義一個函數(shù)sum_squares,給定任何兩個數(shù)字作為參數(shù),返回它們的平方之和:

>>> def sum_squares(x, y):
        return add(square(x), square(y))
>>> sum_squares(3, 4)
25

用戶定義的函數(shù)的使用方式與內(nèi)置函數(shù)完全相同。 實(shí)際上,從sum_squares的例子我們可以發(fā)現(xiàn),我們根本無法分辨square是內(nèi)置在解釋器中,是從模塊導(dǎo)入的還是由用戶定義得。

def語句和賦值語句都是將名稱綁定到值,并且任何現(xiàn)有的綁定都將丟失。 例如,下文的g首先指的是沒有參數(shù)的函數(shù),然后是一個數(shù)字,再然后是有兩個參數(shù)的另一個函數(shù)。

>>> def g():
        return 1
>>> g()
1
>>> g = 2
>>> g
2
>>> def g(h, i):
        return h + i
>>> g(1, 2)
3

1.3.1 環(huán)境

我們的Python子集現(xiàn)在已經(jīng)足夠復(fù)雜,但程序的含義還不是很明顯。 如果形式參數(shù)與內(nèi)建函數(shù)具有相同的名稱怎么辦呢? 兩個函數(shù)可以共享名稱嗎? 要解決這些問題,我們必須更詳細(xì)地描述環(huán)境。

表達(dá)式求值的環(huán)境由frame幀的序列組成,它們可以被描述為一些盒子。 每個frame都包含綁定,它們將名稱與其對應(yīng)的值相關(guān)聯(lián)。global frame全局幀只有一個。 賦值和導(dǎo)入語句將條目添加到當(dāng)前環(huán)境的第一幀中。 到目前為止,我們的環(huán)境只包括全局幀。

>>> from math import pi
>>> tau = 2 * pi

環(huán)境圖示可以顯示出當(dāng)前環(huán)境的綁定,以及它們綁定的值。 您可以點(diǎn)擊Online Python Tutor鏈接,將示例加載到Online Python Tutor,這是由Philip Guo創(chuàng)建的用于生成這些環(huán)境圖的工具。

函數(shù)也出現(xiàn)在環(huán)境圖中。 import導(dǎo)入語句將名稱綁定到內(nèi)置函數(shù)。 def語句將名稱綁定到由用戶定義函數(shù)。 導(dǎo)入mul和定義square后的環(huán)境如下:


每個函數(shù)都是以func開頭的行,后跟函數(shù)名和形式參數(shù)。 內(nèi)建函數(shù)(如mul)沒有形式參數(shù)名稱,因此總是使用。

函數(shù)的名稱重復(fù)兩次,一次在幀中,并再次作為函數(shù)本身的一部分。 函數(shù)中出現(xiàn)的名稱稱為intrinsic內(nèi)在名稱。 幀中的名稱是綁定名稱。 兩者之間有區(qū)別:不同的名稱可能指的是相同的函數(shù),但該函數(shù)本身只有一個內(nèi)在名稱。

綁定到幀中的函數(shù)的名稱將會在求值過程中使用。 函數(shù)的內(nèi)在名稱在求值中不起作用。 通過下面的示例,一旦名稱max被綁定到值3,它將不能再被用作為一個函數(shù)。


錯誤消息TypeError'int' object is not callable('int'對象不可調(diào)用)顯示名稱max(當(dāng)前綁定到數(shù)字3)是一個整型而不是一個函數(shù)。 因此,它不能被用作調(diào)用表達(dá)式中的運(yùn)算符。

函數(shù)簽名。 函數(shù)因參數(shù)的數(shù)量不同。 用戶定義的函數(shù)square只有一個參數(shù)x; 提供更多或更少的參數(shù)將導(dǎo)致錯誤。 對函數(shù)的形式參數(shù)的描述被稱為函數(shù)的簽名。

函數(shù)max可以有任意數(shù)量的參數(shù)。 它被渲染為max(...)。 不管有多少個參數(shù),所有內(nèi)置函數(shù)將被呈現(xiàn)為<name>(...)。

1.3.2 調(diào)用用戶定義的函數(shù)

為了求出其操作符為用戶定義函數(shù)的調(diào)用表達(dá)式,Python解釋器遵循了以下計(jì)算過程。與任何調(diào)用表達(dá)式一樣,解釋器將對運(yùn)算符和操作數(shù)表達(dá)式求值,然后將具名函數(shù)應(yīng)用于生成的實(shí)參。

調(diào)用用戶定義的函數(shù)會引入第二個局部幀,它只能訪問該函數(shù)。為了對一些實(shí)參調(diào)用用戶定義的函數(shù):

1.在新的局部幀中,將實(shí)參綁定到的函數(shù)的形式參數(shù)上。
2.在以此幀開頭的環(huán)境中求出函數(shù)體。

函數(shù)體求值的環(huán)境由兩個幀組成:首先是包含形式參數(shù)綁定的局部幀,然后是包含其他所有內(nèi)容的全局幀。函數(shù)的每個實(shí)例都有自己的獨(dú)立局部幀。

為了詳細(xì)說明一個例子,下面描述了相同示例的環(huán)境圖的幾個步驟。執(zhí)行第一個import語句后,只有名稱mul被綁定在全局幀中。


首先,執(zhí)行函數(shù)square的定義語句。 請注意,整個def語句在一個步驟中執(zhí)行。 直到調(diào)用函數(shù)才執(zhí)行函數(shù)體(不是在定義的時候)。

接下來,使用參數(shù)-2調(diào)用square函數(shù),因此創(chuàng)建了一個新的幀,形式參數(shù)x綁定到值-2上。

然后,在當(dāng)前環(huán)境中查找名稱x,它由所示的兩個幀組成。 在這兩種情況下,x為-2,因此square函數(shù)返回4。

square()的幀中的“return value”不是名稱綁定;而是指由創(chuàng)建該幀的函數(shù)調(diào)用所返回的值。

即使在這個簡單的例子中,也會使用兩種不同的環(huán)境。 我們在全局環(huán)境中計(jì)算最上方表達(dá)式square(-2),在通過調(diào)用square創(chuàng)建的環(huán)境中計(jì)算返回表達(dá)式mul(x,x)。xmul都綁定在這個環(huán)境中,但是在不同的幀中。

環(huán)境中的幀順序會影響由表達(dá)式中名稱檢索而返回的值。 我們之前說過,名稱求解為與當(dāng)前環(huán)境中的該名稱相關(guān)聯(lián)的值。 我們現(xiàn)在可以更準(zhǔn)確地說:

我們關(guān)于環(huán)境,名稱和函數(shù)的概念建立了求值模型; 雖然一些機(jī)械細(xì)節(jié)仍然未敲定(例如如何實(shí)現(xiàn)綁定),但我們的模型能準(zhǔn)確而正確地描述解釋器如何求解調(diào)用表達(dá)式。 在第三章中,我們將看到這個模型如何作為藍(lán)圖來實(shí)現(xiàn)編程語言的工作解釋器。

1.3.3 示例:調(diào)用用戶定義的函數(shù)

讓我們再次考慮兩個簡單的函數(shù)定義,并說明用戶定義函數(shù)的調(diào)用表達(dá)式的求解過程。


Python首先求出名稱sum_squares,它綁定到了全局幀中的用戶定義的函數(shù)。 基本的數(shù)字表達(dá)式5和12求值為它們表達(dá)的數(shù)值。

接下來,Python調(diào)用了sum_squares,它引入了將x綁定到5和y綁定到12的局部幀。


sum_squares的函數(shù)體包含以下調(diào)用表達(dá)式:

所有的三個子表達(dá)式在當(dāng)前環(huán)境中進(jìn)行求解,它開頭于標(biāo)記為sum_squares()的幀。 運(yùn)算符子表達(dá)式add是在全局幀中找到的名稱,它綁定到內(nèi)建的加法函數(shù)中。 在調(diào)用加法函數(shù)之前,兩個操作數(shù)子表達(dá)式必須依次求值。 在當(dāng)前以標(biāo)記為sum_squares的幀的環(huán)境中,對兩個操作數(shù)進(jìn)行求值。

operand 0中,square命名了全局幀中的用戶定義的函數(shù),而x則命名為局部幀的數(shù)字5。 Python通過引入另一個將x綁定到5的局部幀來應(yīng)用square 到5。


在這種環(huán)境下,表達(dá)式mul(x,x)計(jì)算為25。

我們的求值過程現(xiàn)在輪到operand 1,其中y的值為12. Python會再次對square的函數(shù)體進(jìn)行求解,此時引入另一個將x綁定到12的局部幀。因此,operand 1求值為144。


最后,對參數(shù)25和144調(diào)用加法得到sum_squares的最終返回值:169。

這個例子說明了迄今為止我們發(fā)展出來的許多基本概念。 名稱綁定到值,這些值分布在許多獨(dú)立的局部幀,以及包含共享名稱的單個全局幀中。 每次調(diào)用一個函數(shù)時都會引入一個新的局部幀,即使是同一個函數(shù)被調(diào)用兩次的情況。

所有這些機(jī)制的存在,都是為了在程序執(zhí)行期間確保在正確的時間將名稱解析為正確的值。 這個例子說明了為什么我們的模型需要引入的復(fù)雜性。 所有三個局部幀都包含x的綁定,但該名稱綁定到不同的幀中的不同值上。 局部幀分離了這些名稱。

1.3.4 局部名稱

函數(shù)實(shí)現(xiàn)的一個細(xì)節(jié)是實(shí)現(xiàn)者對函數(shù)的形式參數(shù)的名稱的選擇不應(yīng)影響函數(shù)行為。 因此,以下函數(shù)應(yīng)提供相同的行為:

>>> def square(x):
        return mul(x, x)
>>> def square(y):
        return mul(y, y)

這個原則 -- 函數(shù)應(yīng)該與其編寫者選擇的參數(shù)名稱無關(guān) --對編程語言有重要的意義。 最簡單的是函數(shù)的參數(shù)名稱必須保留在函數(shù)體的局部范圍內(nèi)。

如果參數(shù)不是它們各自函數(shù)主體的局部參數(shù),那么在square中的參數(shù)x可能與sum_squares中的參數(shù)x混淆。 嚴(yán)格來說,這并不是問題所在:在不同的局部幀中的x綁定是不相關(guān)的。 我們的計(jì)算模型經(jīng)過嚴(yán)謹(jǐn)?shù)脑O(shè)計(jì),以確保這種獨(dú)立性。

局部名稱的作用范圍僅限于定義它的用戶定義函數(shù)體中。 當(dāng)一個名稱不能再被訪問時,它就離開了作用域。 這種作用域范圍界定行為不是我們模型的新事實(shí); 這是環(huán)境的工作方式的結(jié)果。

1.3.5 選擇名稱

名稱的可修改性并不意味著形式參數(shù)名稱不重要。相反,精心選擇的函數(shù)和參數(shù)名稱對于程序的可解釋性至關(guān)重要!

以下指導(dǎo)原則來自于Python代碼的樣式指南,它可以作為所有(非反叛)Python程序員的指南。這些共享的約定使開發(fā)者社區(qū)的成員之間的溝通能夠順利進(jìn)行。遵循這些約定有一些副作用,您將發(fā)現(xiàn)您的代碼在內(nèi)部變得一致。

1.函數(shù)名稱應(yīng)該小寫,用下劃線分隔。提倡描述性名稱。
2.函數(shù)名稱通常反映解釋器應(yīng)用于參數(shù)的操作(例如,print,add,square)或結(jié)果(例如,max,abs,sum)。
3.參數(shù)名稱應(yīng)該小寫,單詞用下劃線分隔。單字名稱是首選。
4.參數(shù)名稱應(yīng)該反映參數(shù)在函數(shù)中的作用。
5.當(dāng)作用明確時,單字參數(shù)名稱可以接受,但避免使用l(小寫的L)和O(大寫的o),或I(大寫的i)以避免與數(shù)字混淆。

這些指南也有許多例外,即使在Python標(biāo)準(zhǔn)庫中也是如此。像英語的詞匯一樣,Python繼承了各種貢獻(xiàn)者的詞匯,而結(jié)果并不總是一致的。

1.3.6 作為抽象的函數(shù)

盡管函數(shù)sum_squares很簡單,但它可以說明用戶定義函數(shù)最強(qiáng)大的屬性。 函數(shù)sum_squares是根據(jù)函數(shù)square定義的,但僅依賴于square的輸入?yún)?shù)與其輸出值之間的關(guān)系。

我們可以編寫sum_squares,而不用考慮自己如何計(jì)算平方數(shù)。 平方數(shù)計(jì)算的細(xì)節(jié)被隱藏了,可以以后考慮。 事實(shí)上,就sum_squares而言,square并不是一個特定的函數(shù)體,而是某個函數(shù)的抽象。 在這個抽象層次上,任何能計(jì)算平方數(shù)的函數(shù)都是等價的。

因此,在只考慮返回值的情況下,以下兩個計(jì)算平方數(shù)的函數(shù)是難以區(qū)分的:它們都是接受數(shù)值參數(shù)并返回該數(shù)的平方值。

>>> def square(x):
        return mul(x, x)
>>> def square(x):
        return mul(x, x-1) + x

換句話說,函數(shù)定義能夠隱藏細(xì)節(jié)。函數(shù)的用戶可能沒有自己編寫功能,但從另一個程序員那里獲得它作為“黑盒子”。用戶只需要調(diào)用,不需要知道實(shí)現(xiàn)該功能的細(xì)節(jié)。 Python庫具有此屬性。許多開發(fā)人員使用這里定義的函數(shù),但很少有人去研究它們的實(shí)現(xiàn)。

函數(shù)式抽象。要掌握函數(shù)式抽象,需要考慮三個核心屬性。函數(shù)的域是它可以使用的參數(shù)集合;函數(shù)的范圍是返回值的集合;函數(shù)的功能是它在輸入和輸出之間的關(guān)系(以及它可能產(chǎn)生的任何副作用)。通過函數(shù)的域,范圍和意圖理解函數(shù)式抽象對于在復(fù)雜程序中正確使用它們至關(guān)重要。

例如,我們用于實(shí)現(xiàn)sum_squares的任何平方函數(shù)應(yīng)具有以下屬性:

1.域是任意單個實(shí)數(shù)。
2.范圍是任意非負(fù)實(shí)數(shù)。
3.功能是輸出是輸入的平方。
這些屬性并沒有描述功能如何實(shí)現(xiàn)的細(xì)節(jié)部分,它們已經(jīng)被抽象了。

1.3.7 運(yùn)算符

算術(shù)運(yùn)算符(如+-)在第一個例子中提供了組合方法,但是我們還沒有定義一個包含這些運(yùn)算符的表達(dá)式定義求值過程。

帶有中綴運(yùn)算符的Python表達(dá)式都有自己的求值過程,但是您經(jīng)??梢哉J(rèn)為它們是調(diào)用表達(dá)式的快捷方式。 當(dāng)您看到

>>> 2 + 3
5

的時候,可以簡單地認(rèn)為它是

>>> add(2, 3)
5

的快捷方式。中綴符號可以嵌套,就像調(diào)用表達(dá)式一樣。 Python運(yùn)算符優(yōu)先級采用了常規(guī)的數(shù)學(xué)規(guī)則,它指導(dǎo)了如何用多個運(yùn)算符來求解復(fù)合表達(dá)式。

>>> 2 + 3 * 4 + 5
19

它和以下表達(dá)式的求解結(jié)果完全相同

>>> add(add(2, mul(3, 4)), 5)
19

調(diào)用表達(dá)式中的嵌套比運(yùn)算符版本更加明顯,但也更難以閱讀。 Python還允許使用括號對子表達(dá)式進(jìn)行分組,以覆蓋通常的優(yōu)先級規(guī)則,或使表達(dá)式的嵌套結(jié)構(gòu)更加明顯。

>>> (2 + 3) * (4 + 5)
45

它和以下表達(dá)式的求解結(jié)果完全相同

>>> mul(add(2, 3), add(4, 5))
45

對于除法,Python提供了兩個中綴運(yùn)算符:///。 前者是常規(guī)除法,及時是整除,結(jié)果也是浮點(diǎn)數(shù):

>>> 5 / 4
1.25
>>> 8 / 4
2.0

后一個運(yùn)算符//,直接將結(jié)果舍入到一個整數(shù)

>>> 5 // 4
1
>>> -5 // 4
-2

這兩個運(yùn)算符是對truedivfloordiv函數(shù)的快捷方式。

>>> from operator import truediv, floordiv
>>> truediv(5, 4)
1.25
>>> floordiv(5, 4)
1

您應(yīng)該在程序中自由使用中綴操作符和括號。對于簡單的算術(shù)運(yùn)算,Python在慣例上傾向于使用運(yùn)算符而不是調(diào)用表達(dá)式。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內(nèi)容