深入Python函數編程的一些特性_python

來源:互聯網
上載者:User

綁定

細心的讀者可能記得我在 第 1 部分的函數技術中指出的限制。特別在 Python 中不能避免表示函數運算式的名稱的重新綁定。在 FP 中,名稱通常被理解為較長運算式的縮寫,但這一說法暗示著“同一運算式總是求出相同的值”。如果標記的名稱重新被綁定,這一暗示便不成立。例如,讓我們定義一些在函數編程中要用到的快捷運算式,比如:
清單 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

不幸的是,完全相同的運算式 sum2(range(10)) 在程式中的兩處求得兩個不同的值,即使該運算式自身並沒有在其參數中使用任何可變變數。

幸運的是, functional 模組提供了稱為 Bindings 的類(向 Keller 提議)來防止這樣的重新綁定(至少在偶然情況下,Python 不會阻止一心想要解除綁定的程式員)。然而使用 Bindings 需要一些額外的文法,這樣意外就不太容易發生。在 functional 模組的樣本中,Keller 將 Bindings 執行個體命名為 let (我假定在 ML 家族語言的 let 關鍵詞的後面)。 例如,我們會這樣做:
清單 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." % namefunctional.BindingError: Binding 'car' cannot be modified.>>> car(range(10))0

很明顯,真正的程式必須做一些設定來捕獲“綁定錯誤”,而且他們被拋出也避免了一類問題的出現。

與 Bindings 一起, functional 提供 namespace 函數從 Bindings 執行個體中擷取命名空間(實際上是個字典)。如果希望在 Bindings 中定義的(不可變)命名空間中運算一個運算式,這非常容易實現。Python 的 eval() 函數允許在命名空間中進行運算。 讓我們通過一個樣本來弄清楚:
清單 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 中有個有趣的概念 -- 閉包。實際上,閉包對許多開發人員都非常有趣,即使在如 Perl 和 Ruby 這樣的無函數語言中也都包括閉包這一功能。而且,Python 2.1 目前正想加入詞彙範圍限制功能,這一功能將提供閉包的大部分功能。

什麼 是閉包呢? Steve Majewski 最近在 Python 新聞群組提供了對這一概念的很好描述:

    對象是附帶過程的資料……閉包是附帶資料的過程。

閉包就象是 FP 的 Jekyll 對於 OOP 的 Hyde (角色或者也可能對調)。閉包類似對象樣本,是一種將一大批資料和功能封裝在一起的一種方式。

讓我們回到先前的地方瞭解對象和閉包解決什麼問題,同時瞭解一下問題如果沒有這兩樣是如何解決的。函數返回的結果往往是由其計算中使用的上下文決定的。最常見的 -- 也可能是最明顯的 -- 指定內容相關的方法是向函數傳遞某些參數,通知函數處理什麼值。但有時候“背景”和“前景”參數有著本質的區別 -- 在這特定時刻函數正在處理的和函數為多段潛在調用而“配置”之間的區別。

當把重點放在前景的時候,有許多處理背景的方法。其中一種是簡單“咬出子彈”的方法,在每次調用的時候傳遞函數需要的每一個參數。這種方法通常在調用鏈中,只要在某些地方有可能需要值,就會傳遞一些值(或帶有多成員的結構)。以下是一個小樣本:
清單 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 downstream17

在 cargo 樣本的 b() 中, n 除了起到傳遞到 c() 的作用外並無其他作用。另一種方法將使用全域變數:
清單 5. 顯示全域變數的 Python 部分

>>> N = 10>>>         defaddN        (i):...           global         N...           return         i+N...>>> addN(7)          # Add global N to argument17>>> N = 20>>> addN(6)          # Add global N to argument26全域變數 N 在任何希望調用 addN() 的時候起作用,但沒有必要明確地傳遞全域背景“上下文”。另一個更 Python 專用的技術是將一個變數在定義時“凍結”入一個使用預設參數的函數:清單 6. 顯示凍結變數的 Python 部分>>> N = 10>>>         defaddN        (i, n=N):...           return         i+n...>>> addN(5)          # Add 1015>>> N = 20>>> addN(6)          # Add 10 (current N doesn't matter)16

凍結變數本質上就是閉包。某些資料被“隸屬”於 addN() 函數。對於完整的閉包,當定義 addN() 的時候,所有的資料在調用的時候都將可用。然而,在這個樣本(或者許多更健壯的樣本)中,使用預設的參數就能簡單的夠用了。 addN() 從未使用的變數並不會對其計算造成影響。

接著讓我們來看一個更接近真實問題的 OOP 方法。年份的時間是我想起了那些“會見”風格的收集各種資料的稅收程式 -- 不必有特定的順序 -- 最終使用全部資料來計算。讓我們建立一個簡單的版本:
清單 7. Python 風格的稅收計算類/樣本

class         TaxCalc:          deftaxdue        (self):            return         (self.income-self.deduct)*self.ratetaxclass = TaxCalc()taxclass.income = 50000taxclass.rate = 0.30taxclass.deduct = 10000        print         "Pythonic OOP taxes due =", taxclass.taxdue()

在 TaxCalc 類(或其執行個體)中,能收集一些資料 -- 可以以任意順序 -- 一旦獲得了所需的所有元素,就能調用這一對象的方法來完成這一大批資料的計算。所有一切都在執行個體中,而且,不同樣本攜帶不同的資料。建立多樣本和區別它們的資料的可能性不可能存在於"全域變數"或"凍結變數"方法中。"cargo" 方法能處理這個問題,但對於擴充的樣本來說,我們看到它可能是開始傳遞各種值的必要條件了。既然我們已講到這,注意傳遞訊息的 OPP 風格是如何處理的也非常有趣(Smalltalk 或 Self 與此類似,一些我使用的 OOP xBase 變數也是如此):
清單 8. Smalltalk 風格 (Python) 的稅收計算

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()

用每個 "setter" 來返回 self 使我們能把“現有的”東西看作是每個方法應用的結果。這與 FP 閉包方法有許多有趣的相似點。

有了 Xoltar 工具包,我們就能建立具有所期望的合并資料與函數特性的完整的閉包,同時還允許多段閉包(nee 對象)來包含不同的包:
清單 9. Python 函數風格的稅收計算

from         functional         import         *taxdue    =         lambda        : (income-deduct)*rateincomeClosure =         lambda         income,taxdue: closure(taxdue)deductClosure =         lambda         deduct,taxdue: closure(taxdue)rateClosure  =         lambda         rate,taxdue: closure(taxdue)taxFP = taxduetaxFP = 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)))()

我們定義的每一個閉包函數都攜帶了函數範圍內定義的任何值,然後將這些值綁定到函數對象的全域範圍。然而,函數的全域範圍看上去不必與實際模組的全域範圍相同,同時與不同閉包的“全域”範圍也不相同。閉包只是簡單地“攜帶資料”。

在樣本中,我們使用了一些特殊函數在閉包範圍 (income、deduct、rate) 內放入了特定綁定。修改設計以在範圍內放入任何綁定也非常簡單。我們還可以在樣本中使用具有細微差別的不同函數風格,當然這隻是為了好玩。第一個成功的將附加值綁定入閉包範圍內;使 taxFP 成為可變,這些“加入到閉包”的行可以任意順序出現。然而,如果要使用如 tax_with_Income 這樣的不可變名稱,就必須將綁定行按照一定順序排列,然後將前面的綁定傳遞到下一個。無論如何,一旦必需的一切被綁定入閉包的範圍內,我們就調用 "seeded" 函數。

第二種風格看上去更接近 Lisp,(對我來說更像圓括弧)。如果不考慮美觀,第二種風格中發生了二件有趣的事情。第一件是名稱綁定完全被避免了。第二種風格是一個單一運算式而不使用語句(請參閱 第 1 部分,討論為什麼這樣會有問題)。

其它有關“Lisp 風格”閉包使用的有趣例子是其與上文提到的“Smalltalk 風格”訊息傳遞方法有多少類似。兩者累積了值和調用 taxdue() 函數/方法(如果沒有正確的資料,兩者在這些原始版本中都將報錯)。“Smalltalk 風格”在每一步之間傳遞對象,而“Lisp 風格”傳遞一個連續。但若是更深一層理解,函數和物件導向編程大部分都是這樣。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.