深入Python函數(shù)編程的一些特性
綁定
細(xì)心的讀者可能記得我在 第 1 部分的函數(shù)技術(shù)中指出的限制。特別在 Python 中不能避免表示函數(shù)表達(dá)式的名稱的重新綁定。在 FP 中,名稱通常被理解為較長表達(dá)式的縮寫,但這一說法暗示著“同一表達(dá)式總是求出相同的值”。如果標(biāo)記的名稱重新被綁定,這一暗示便不成立。例如,讓我們定義一些在函數(shù)編程中要用到的快捷表達(dá)式,比如:
清單 1. 以下 Python FP 部分的重新綁定要造成故障
>>> car =
lambda
lst: lst[0]
>>> cdr =
lambda
lst: lst[1:]
>>> sum2 =
lambda
lst: car(lst)+car(cdr(lst))
>>> sum2(range(10))
1
>>> car =
lambda
lst: lst[2]
>>> sum2(range(10))
5
不幸的是,完全相同的表達(dá)式 sum2(range(10)) 在程序中的兩處求得兩個(gè)不同的值,即使該表達(dá)式自身并沒有在其參數(shù)中使用任何可變變量。
幸運(yùn)的是, functional 模塊提供了稱為 Bindings 的類(向 Keller 提議)來防止這樣的重新綁定(至少在偶然情況下,Python 不會(huì)阻止一心想要解除綁定的程序員)。然而使用 Bindings 需要一些額外的語法,這樣意外就不太容易發(fā)生。在 functional 模塊的示例中,Keller 將 Bindings 實(shí)例命名為 let (我假定在 ML 家族語言的 let 關(guān)鍵詞的后面)。 例如,我們會(huì)這樣做:
清單 2. 具有安全重新綁定的 Python FP 部分
>>>
from
functional
import
*
>>> let = Bindings()
>>> let.car =
lambda
lst: lst[0]
>>> let.car =
lambda
lst: lst[2]
Traceback (innermost last):
File "<stdin>", line 1,
in
?
File "d:\tools\functional.py", line 976,
in
__setattr__
raise
BindingError, "Binding '%s' cannot be modified." % name
functional.BindingError: Binding 'car' cannot be modified.
>>> car(range(10))
0
很明顯,真正的程序必須做一些設(shè)置來捕獲“綁定錯(cuò)誤”,而且他們被拋出也避免了一類問題的出現(xiàn)。
與 Bindings 一起, functional 提供 namespace 函數(shù)從 Bindings 實(shí)例中獲取命名空間(實(shí)際上是個(gè)字典)。如果希望在 Bindings 中定義的(不可變)命名空間中運(yùn)算一個(gè)表達(dá)式,這非常容易實(shí)現(xiàn)。Python 的 eval() 函數(shù)允許在命名空間中進(jìn)行運(yùn)算。 讓我們通過一個(gè)示例來弄清楚:
清單 3. 使用不可變命名空間的 Python FP 部分
>>> let = Bindings()
# "Real world" function names
>>> let.r10 = range(10)
>>> let.car =
lambda
lst: lst[0]
>>> let.cdr =
lambda
lst: lst[1:]
>>> eval('car(r10)+car(cdr(r10))', namespace(let))
>>> inv = Bindings()
# "Inverted list" function names
>>> inv.r10 = let.r10
>>> inv.car =
lambda
lst: lst[-1]
>>> inv.cdr =
lambda
lst: lst[:-1]
>>> eval('car(r10)+car(cdr(r10))', namespace(inv))
17
閉包
FP 中有個(gè)有趣的概念 -- 閉包。實(shí)際上,閉包對許多開發(fā)人員都非常有趣,即使在如 Perl 和 Ruby 這樣的無函數(shù)語言中也都包括閉包這一功能。而且,Python 2.1 目前正想加入詞匯范圍限制功能,這一功能將提供閉包的大部分功能。
什么 是閉包呢? Steve Majewski 最近在 Python 新聞組提供了對這一概念的很好描述:
對象是附帶過程的數(shù)據(jù)……閉包是附帶數(shù)據(jù)的過程。
閉包就象是 FP 的 Jekyll 對于 OOP 的 Hyde (角色或者也可能對調(diào))。閉包類似對象示例,是一種將一大批數(shù)據(jù)和功能封裝在一起的一種方式。
讓我們回到先前的地方了解對象和閉包解決什么問題,同時(shí)了解一下問題如果沒有這兩樣是如何解決的。函數(shù)返回的結(jié)果往往是由其計(jì)算中使用的上下文決定的。最常見的 -- 也可能是最明顯的 -- 指定上下文的方法是向函數(shù)傳遞某些參數(shù),通知函數(shù)處理什么值。但有時(shí)候“背景”和“前景”參數(shù)有著本質(zhì)的區(qū)別 -- 在這特定時(shí)刻函數(shù)正在處理的和函數(shù)為多段潛在調(diào)用而“配置”之間的區(qū)別。
當(dāng)把重點(diǎn)放在前景的時(shí)候,有許多處理背景的方法。其中一種是簡單“咬出子彈”的方法,在每次調(diào)用的時(shí)候傳遞函數(shù)需要的每一個(gè)參數(shù)。這種方法通常在調(diào)用鏈中,只要在某些地方有可能需要值,就會(huì)傳遞一些值(或帶有多成員的結(jié)構(gòu))。以下是一個(gè)小示例:
清單 4. 顯示 cargo 變量的 Python 部分
>>>
defa
(n):
... add7 = b(n)
...
return
add7
...
>>>
defb
(n):
... i = 7
... j = c(i,n)
...
return
j
...
>>>
defc
(i,n):
...
return
i+n
...
>>> a(10)
# Pass cargo value for use downstream
17
在 cargo 示例的 b() 中, n 除了起到傳遞到 c() 的作用外并無其他作用。另一種方法將使用全局變量:
清單 5. 顯示全局變量的 Python 部分
>>> N = 10
>>>
defaddN
(i):
...
global
N
...
return
i+N
...
>>> addN(7)
# Add global N to argument
17
>>> N = 20
>>> addN(6)
# Add global N to argument
26
全局變量 N 在任何希望調(diào)用 addN() 的時(shí)候起作用,但沒有必要明確地傳遞全局背景“上下文”。另一個(gè)更 Python 專用的技術(shù)是將一個(gè)變量在定義時(shí)“凍結(jié)”入一個(gè)使用默認(rèn)參數(shù)的函數(shù):
清單 6. 顯示凍結(jié)變量的 Python 部分
>>> N = 10
>>>
defaddN
(i, n=N):
...
return
i+n
...
>>> addN(5)
# Add 10
15
>>> N = 20
>>> addN(6)
# Add 10 (current N doesn't matter)
16
凍結(jié)變量本質(zhì)上就是閉包。某些數(shù)據(jù)被“隸屬”于 addN() 函數(shù)。對于完整的閉包,當(dāng)定義 addN() 的時(shí)候,所有的數(shù)據(jù)在調(diào)用的時(shí)候都將可用。然而,在這個(gè)示例(或者許多更健壯的示例)中,使用默認(rèn)的參數(shù)就能簡單的夠用了。 addN() 從未使用的變量并不會(huì)對其計(jì)算造成影響。
接著讓我們來看一個(gè)更接近真實(shí)問題的 OOP 方法。年份的時(shí)間是我想起了那些“會(huì)見”風(fēng)格的收集各種數(shù)據(jù)的稅收程序 -- 不必有特定的順序 -- 最終使用全部數(shù)據(jù)來計(jì)算。讓我們創(chuàng)建一個(gè)簡單的版本:
清單 7. Python 風(fēng)格的稅收計(jì)算類/示例
class
TaxCalc:
deftaxdue
(self):
return
(self.income-self.deduct)*self.rate
taxclass = TaxCalc()
taxclass.income = 50000
taxclass.rate = 0.30
taxclass.deduct = 10000
print
"Pythonic OOP taxes due =", taxclass.taxdue()
在 TaxCalc 類(或其實(shí)例)中,能收集一些數(shù)據(jù) -- 可以以任意順序 -- 一旦獲得了所需的所有元素,就能調(diào)用這一對象的方法來完成這一大批數(shù)據(jù)的計(jì)算。所有一切都在實(shí)例中,而且,不同示例攜帶不同的數(shù)據(jù)。創(chuàng)建多示例和區(qū)別它們的數(shù)據(jù)的可能性不可能存在于"全局變量"或"凍結(jié)變量"方法中。"cargo" 方法能處理這個(gè)問題,但對于擴(kuò)展的示例來說,我們看到它可能是開始傳遞各種值的必要條件了。既然我們已講到這,注意傳遞消息的 OPP 風(fēng)格是如何處理的也非常有趣(Smalltalk 或 Self 與此類似,一些我使用的 OOP xBase 變量也是如此):
清單 8. Smalltalk 風(fēng)格 (Python) 的稅收計(jì)算
class
TaxCalc:
deftaxdue
(self):
return
(self.income-self.deduct)*self.rate
defsetIncome
(self,income):
self.income = income
return
self
defsetDeduct
(self,deduct):
self.deduct = deduct
return
self
defsetRate
(self,rate):
self.rate = rate
return
self
print
"Smalltalk-style taxes due =", \
TaxCalc().setIncome(50000).setRate(0.30).setDeduct(10000).taxdue()
用每個(gè) "setter" 來返回 self 使我們能把“現(xiàn)有的”東西看作是每個(gè)方法應(yīng)用的結(jié)果。這與 FP 閉包方法有許多有趣的相似點(diǎn)。
有了 Xoltar 工具包,我們就能創(chuàng)建具有所期望的合并數(shù)據(jù)與函數(shù)特性的完整的閉包,同時(shí)還允許多段閉包(nee 對象)來包含不同的包:
清單 9. Python 函數(shù)風(fēng)格的稅收計(jì)算
from
functional
import
*
taxdue =
lambda
: (income-deduct)*rate
incomeClosure =
lambda
income,taxdue: closure(taxdue)
deductClosure =
lambda
deduct,taxdue: closure(taxdue)
rateClosure =
lambda
rate,taxdue: closure(taxdue)
taxFP = taxdue
taxFP = incomeClosure(50000,taxFP)
taxFP = rateClosure(0.30,taxFP)
taxFP = deductClosure(10000,taxFP)
print
"Functional taxes due =",taxFP()
print
"Lisp-style taxes due =", \
incomeClosure(50000,
rateClosure(0.30,
deductClosure(10000, taxdue)))()
我們定義的每一個(gè)閉包函數(shù)都攜帶了函數(shù)范圍內(nèi)定義的任何值,然后將這些值綁定到函數(shù)對象的全局范圍。然而,函數(shù)的全局范圍看上去不必與實(shí)際模塊的全局范圍相同,同時(shí)與不同閉包的“全局”范圍也不相同。閉包只是簡單地“攜帶數(shù)據(jù)”。
在示例中,我們使用了一些特殊函數(shù)在閉包范圍 (income、deduct、rate) 內(nèi)放入了特定綁定。修改設(shè)計(jì)以在范圍內(nèi)放入任何綁定也非常簡單。我們還可以在示例中使用具有細(xì)微差別的不同函數(shù)風(fēng)格,當(dāng)然這只是為了好玩。第一個(gè)成功的將附加值綁定入閉包范圍內(nèi);使 taxFP 成為可變,這些“加入到閉包”的行可以任意順序出現(xiàn)。然而,如果要使用如 tax_with_Income 這樣的不可變名稱,就必須將綁定行按照一定順序排列,然后將前面的綁定傳遞到下一個(gè)。無論如何,一旦必需的一切被綁定入閉包的范圍內(nèi),我們就調(diào)用 "seeded" 函數(shù)。
第二種風(fēng)格看上去更接近 Lisp,(對我來說更像圓括號(hào))。如果不考慮美觀,第二種風(fēng)格中發(fā)生了二件有趣的事情。第一件是名稱綁定完全被避免了。第二種風(fēng)格是一個(gè)單一表達(dá)式而不使用語句(請參閱 第 1 部分,討論為什么這樣會(huì)有問題)。
其它有關(guān)“Lisp 風(fēng)格”閉包使用的有趣例子是其與上文提到的“Smalltalk 風(fēng)格”消息傳遞方法有多少類似。兩者累積了值和調(diào)用 taxdue() 函數(shù)/方法(如果沒有正確的數(shù)據(jù),兩者在這些原始版本中都將報(bào)錯(cuò))?!癝malltalk 風(fēng)格”在每一步之間傳遞對象,而“Lisp 風(fēng)格”傳遞一個(gè)連續(xù)。但若是更深一層理解,函數(shù)和面向?qū)ο缶幊檀蟛糠侄际沁@樣。
相關(guān)文章
10個(gè)python爬蟲入門實(shí)例(小結(jié))
這篇文章主要介紹了10個(gè)python爬蟲入門實(shí)例(小結(jié)),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
numpy使用技巧之?dāng)?shù)組過濾實(shí)例代碼
這篇文章主要介紹了numpy使用技巧之?dāng)?shù)組過濾實(shí)例代碼,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02
Python3標(biāo)準(zhǔn)庫之dbm UNIX鍵-值數(shù)據(jù)庫問題
dbm是面向DBM數(shù)據(jù)庫的一個(gè)前端,DBM數(shù)據(jù)庫使用簡單的字符串值作為鍵來訪問包含字符串的記錄。這篇文章主要介紹了Python3標(biāo)準(zhǔn)庫:dbm UNIX鍵-值數(shù)據(jù)庫的相關(guān)知識(shí),需要的朋友可以參考下2020-03-03
python 中關(guān)于pycharm選擇運(yùn)行環(huán)境的問題
這篇文章主要介紹了python 中關(guān)于pycharm選擇運(yùn)行環(huán)境的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10
python統(tǒng)計(jì)列表中元素出現(xiàn)次數(shù)的三種方法
這篇文章主要介紹了python統(tǒng)計(jì)列表中元素出現(xiàn)次數(shù)的三種方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-08-08
據(jù)Python爬蟲不靠譜預(yù)測可知今年雙十一銷售額將超過6000億元
已經(jīng)是十一月十號(hào)了,雙十一即將到來,電商早已預(yù)熱多日,為了在實(shí)戰(zhàn)中獲得能力的提升,本篇文章手把手帶你用Python來預(yù)測一下今年雙十一的銷售額將會(huì)達(dá)到多少,大家可以在過程中查缺補(bǔ)漏,提升水平2021-11-11

