歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux資訊 >> 更多Linux >> Python 中的函數編程-----剛開始涉足函數編程?

Python 中的函數編程-----剛開始涉足函數編程?

日期:2017/2/27 14:21:32   编辑:更多Linux
  專欄繼續 David 對 Python 中的函數編程 (FP) 的介紹。請閱讀本文對解決編程問題的不同范例的介紹,在介紹過程中 David 將闡述幾個中高級的 FP 概念。 在第 1 部分,有關函數編程的前一個專欄中,我介紹了一些 FP 的基礎概念。本專欄將進一步深入研究這一概念性相當強的領域。對於我們的研究,Bryn Keller 的 “Xoltar 工具包” 將提供有用的幫助。Keller 把很多 FP 的優勢都集中在一個包括純 Python 技術執行的小巧模塊中。除了 functional 模塊,Xoltar 工具包還包括 lazy 模塊,該模塊支持“只在需要時”賦值的結構。許多傳統函數語言也具有緩式賦值,因此在這些組件中,Xoltar 工具包使您獲得象 Haskell 這樣的函數語言該有的大部分功能。 綁定 細心的讀者可能記得我在第 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." % name functional.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 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 在任何希望調用 addN() 的時候起作用,但沒有必要明確地傳遞全局背景“上下文”。另一個更 Python 專用的技術是將一個變量在定義時“凍結”入一個使用默認參數的函數: 清單 6. 顯示凍結變量的 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 凍結變量本質上就是閉包。某些數據被“隸屬”於 addN() 函數。對於完整的閉包,當定義 addN() 的時候,所有的數據在調用的時候都將可用。然而,在這個示例(或者許多更健壯的示例)中,使用默認的參數就能簡單的夠用了。 addN() 從未使用的變量並不會對其計算造成影響。 接著讓我們來看一個更接近真實問題的 OOP 方法。年份的時間是我想起了那些“會見”風格的收集各種數據的稅收程序 -- 不必有特定的順序 -- 最終使用全部數據來計算。讓我們創建一個簡單的版本: 清單 7. Python 風格的稅收計算類/示例 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 類(或其實例)中,能收集一些數據 -- 可以以任意順序 -- 一旦獲得了所需的所有元素,就能調用這一對象的方法來完成這一大批數據的計算。所有一切都在實例中,而且,不同示例攜帶不同的數據。創建多示例和區別它們的數據的可能性不可能存在於"全局變量"或"凍結變量"方法中。"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)*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)))() 我們定義的每一個閉包函數都攜帶了函數范圍內定義的任何值,然後將這些值綁定到函數對象的全局范圍。然而,函數的全局范圍看上去不必與實際模塊的全局范圍相同,同時與不同閉包的“全局”范圍也不相同。閉包只是簡單地“攜帶數據”。 在示例中,我們使用了一些特殊函數在閉包范圍 (income、deduct、rate) 內放入了特定綁定。修改設計以在范圍內放入任何綁定也非常簡單。我們還可以在示例中使用具有細微差別的不同函數風格,當然這只是為了好玩。第一個成功的將附加值綁定入閉包范圍內;使 taxFP 成為可變,這些“加入到閉包”的行可以任意順序出現。然而,如果要使用如 tax_with_Income 這樣的不可變名稱,就必須將綁定行按照一定順序排列,然後將前面的綁定傳遞到下一個。無論如何,一旦必需的一切被綁定入閉包的范圍內,我們就調用 "seeded" 函數。 第二種風格看上去更接近 Lisp,(對我來說更像圓括號)。如果不考慮美觀,第二種風格中發生了二件有趣的事情。第一件是名稱綁定完全被避免了。第二種風格是一個單一表達式而不使用語句(請參閱第 1 部分,討論為什麼這樣會有問題)。 其它有關“Lisp 風格”閉包使用的有趣例子是其與上文提到的“Smalltalk 風格”消息傳遞方法有多少類似。兩者累積了值和調用 taxdue() 函數/方法(如果沒有正確的數據,兩者在這些原始版本中都將報錯)。“Smalltalk 風格”在每一步之間傳遞對象,而“Lisp 風格”傳遞一個連續。但若是更深一層理解,函數和面向對象編程大部分都是這樣。 尾遞歸 在這一部分中,我們已經完成了函數編程領域的大部分內容。剩下的比以前(這節的題目是個小玩笑;可惜的是,它的概念在文章內沒能解釋)更少(可能更簡單?)。閱讀 functional 模塊的代碼是繼續探索大量 FP 概念的好方法。這個模塊有很多注釋,並對其大部分函數和類提供了示例。本專欄沒能涉及一些簡化的元函數,它們使其它函數的組合和交互更容易處理。這些對於一個對函數范例不斷探索的 Python 程序員來說非常值得研究。 參考資料 * David 的前一專欄,有關在 Python 進行函數編程,是對本文的介紹。 * Bryn Keller 的 "xoltar toolkit",它包括了模塊 functional,添加了大量有用的對 Python 的 FP 擴展。因為 functional 模塊本身完全是用 Python 編寫的,所以它所做的在 Python 本身中已經可能存在。但 Keller 也指出了一組非常緊密集成的擴展,簡潔定義中帶有許多能力。 * Peter Norvig 撰寫了一篇有意思的文章,Python for Lisp Programmers。但他的重點與我這一專欄的觀點有些相反,它提供了 Python 和 Lisp 之間非常好的常規比較。 * comp.lang.functional 常見問題是了解函數編程的一個良好開始。 * 我發現用 Haskell 比用 Lisp/Scheme 更容易掌握函數編程(即使如果只在 Emacs 中,後者可能使用得更廣泛)。其它 Python 程序員可能由於沒有那麼多的括號和前綴 (Polish) 操作符會輕松許多。 * 一本非常有用的介紹性書籍是 Haskell: The Craft of Functional Programming (2nd Edition), Simon Thompson (Addison-Wesley, 1999)。

關於作者 因為沒有直覺的概念是空洞的,沒有概念的直覺是盲目的,David Mertz 希望在他的辦公室裡放置彌爾頓的石膏像。現在他開始計劃他的生日。可以通過 [email protected] 與 David 聯系;在 http://gnosis.cx/publish/ 上詳細介紹了他的生活。非常歡迎對過去的、這一篇或將來的專欄文章提出意見和建議。



其它有關“Lisp 風格”閉包使用的有趣例子是其與上文提到的“Smalltalk 風格”消息傳遞方法有多少類似。兩者累積了值和調用 taxdue() 函數/方法(如果沒有正確的數據,兩者在這些原始版本中都將報錯)。“Smalltalk 風格”在每一步之間傳遞對象,而“Lisp 風格”傳遞一個連續。但若是更深一層理解,函數和面向對象編程大部分都是這樣。 尾遞歸 在這一部分中,我們已經完成了函數編程領域的大部分內容。剩下的比以前(這節的題目是個小玩笑;可惜的是,它的概念在文章內沒能解釋)更少(可能更簡單?)。閱讀 functional 模塊的代碼是繼續探索大量 FP 概念的好方法。這個模塊有很多注釋,並對其大部分函數和類提供了示例。本專欄沒能涉及一些簡化的元函數,它們使其它函數的組合和交互更容易處理。這些對於一個對函數范例不斷探索的 Python 程序員來說非常值得研究。 參考資料 * David 的前一專欄,有關在 Python 進行函數編程,是對本文的介紹。 * Bryn Keller 的 "xoltar toolkit",它包括了模塊 functional,添加了大量有用的對 Python 的 FP 擴展。因為 functional 模塊本身完全是用 Python 編寫的,所以它所做的在 Python 本身中已經可能存在。但 Keller 也指出了一組非常緊密集成的擴展,簡潔定義中帶有許多能力。 * Peter Norvig 撰寫了一篇有意思的文章,Python for Lisp Programmers。但他的重點與我這一專欄的觀點有些相反,它提供了 Python 和 Lisp 之間非常好的常規比較。 * comp.lang.functional 常見問題是了解函數編程的一個良好開始。 * 我發現用 Haskell 比用 Lisp/Scheme 更容易掌握函數編程(即使如果只在 Emacs 中,後者可能使用得更廣泛)。其它 Python 程序員可能由於沒有那麼多的括號和前綴 (Polish) 操作符會輕松許多。 * 一本非常有用的介紹性書籍是 Haskell: The Craft of Functional Programming (2nd Edition), Simon Thompson (Addison-Wesley, 1999)。 關於作者 因為沒有直覺的概念是空洞的,沒有概念的直覺是盲目的,David Mertz 希望在他的辦公室裡放置彌爾頓的石膏像。現在他開始計劃他的生日。可以通過 [email protected] 與 David 聯系;在 http://gnosis.cx/publish/ 上詳細介紹了他的生活。非常歡迎對過去的、這一篇或將來的專欄文章提出意見和建議。



Copyright © Linux教程網 All Rights Reserved