隨手扎
用Python實現Callable Class,FP更好寫
This Article has English Version, please goto here to read.
前言
最近,和朋友們在解LeetCode的題目。看著不同人寫出來的程式,也讓我對於一個題目的解法,有更寬廣的視野。
938. Range Sum of BST
URL: https://leetcode.com/problems/range-sum-of-bst/
這篇文章與這個想法,也是受到朋友寫的一個Ruby寫法的啟發。我用同樣的邏輯去寫Python,如下圖:
當然,這題目有更好更快的寫法,但這樣寫更為有趣有意思。
但是,內部隱含的block
函數,從函數型語言的設計原則來看,他是危險、骯髒的。這是因為每次他的執行,都會修改到外部變數s
的的值。(這辦法不夠 純函數 )
因此,我修改成以下方式:
如此,你還可以改寫成一行:
並且,可以簡單的從in-order走訪樹,簡單的換成pre-order或是post-order。而這基本上是安全(純)的。
2019/12/26 新增
剛收到原code的允許,分享Ruby原版寫法給大家:
相較之下,Python不能在宣告變數前就使用值(跟EMCAScript的TDZ(Temporal Dead Zone)很像)。加上縮排的特性,lambda
只能寫的比較簡單,所以需要改用一個內部函式來寫。而Ruby在這裡就可以直接使用匿名函式(或是用Ruby的說法匿名程式區塊(block)?)。不知各位覺得這原汁原味的寫法如何?(相比Ruby的do-end block,Python的lambda有時無法寫多行有些麻煩Orz)
BTW,最近我也開始在看Ruby設計思想的書。還沒看完,但蠻手癢想寫心得的www。以後在分享給大家。
771. Jewels and Stones
URL: https://leetcode.com/problems/jewels-and-stones
而這個問題,也可以很簡單的用Python解決。但這像義大利麵的寫法,並不那麼好閱讀。
受到LiveScript的backcall(<-
)、Clojure、cl-arrows(Common Lisp)的箭頭函式(->
)啟發,我寫了一個pipe
函式。(當然還有Linux的基本命令pipe
)
OK, 現在更好了解整個程式運作的過程了:
- 過濾單字
- 計算每個單字出現的次數
- 將所有次數加總
pipe
函式,我也曾經在Python-3.8後的f-String實現過。
Callable Class
但有時候,pipe
函式並不那麼好使用。這是因為有些函式需要額外的參數,此時就必須使用lambda
將額外參數包裝起來。如果有個方式,既好讀,又可以更簡單傳遞參數,那一定很好。因此,我寫了CallableWrapper
類別,他會將資料包裝起來,而且可以給定函式執行,並簡單的寫成函式執行鏈。最後,如果為給定可執行的函式(傳入None
),則會回傳最後執行的結果。
class CallableWrapper:
def __init__(self, wrap):
self.wrap = wrap
def __call__(self, f=None, *args):
if f == None:
return self.wrap
return CallableWrapper(f(self.wrap, *args))
所以可以像下面這樣用:
print(CallableWrapper([1,2,3,4,5,6])
(sum, 10)
(float)
()) # => Oupte: 31.0
# or
(CallableWrapper([1,2,3,4,5,6])
(sum, 10)
(float)
()) # => (31.0,)
※ Note1: 由於Python語法的限制,有時你可能會需要使用括號”()“將表達式包裝起來,這樣才可以換行(或是行尾加上反斜線”")。
※ Note2: 還有一個內建函式callable
(全小寫)。這會檢查傳入物件是否可以呼叫執行。如果可以回傳True
,否則回傳False
。(所以…callable(CallableWrapper([1,2,3,4,5])) #=> True
)
Callable Meta-Class
但…如果想要客制一個可以呼叫執行了類別呢?是否有可能簡單的讓新類別同樣又這樣的能力?這是有可能的,也因此我嘗試寫了 meta-class – CallableMeta
和空白內容類別Callable
。寫法如下:
在設計一個新類別時,可以指定metaclass=CallableMeta
(或是更直覺得繼承Callable
)。如果定義了類別的 __call__
會覆寫(override)掉CallableMeta
的行為(不過這也就意味著指不指定metaclass=CallableMeta
沒啥影響了)。或是再加上配合super()
, 讓新定義行為像是pre-do或是post-do. ( 有點像是修飾器(decorator))
有趣的是,function和method有著蠻大的差異。這也為什麼我寫了內部類別。
最後
使用CallableMeta
、Callable
和CallableWrapper
類別。就像是資料實例(instance)有者動態的方法(dynamic methods)。
最後,如果又任何意見,或發現問題(bug),歡迎留言給我。或是更進一步的閱讀:
- This Article's English Version
- 在GitHub Gist看看所有程式。
- 看看pydash有沒有相似的功能。
- (在未來,我可能會把這些玩意兒收集起來,做成Package。歡迎回報錯誤)
或是你可能也會有興趣:
- Python的函數(Function)vs方法(method)
- 覆寫Python操作子(Override Operator)
- Python的資料模型(Data Model)的特殊成員(Special Member)
我去翻看過
pydash.chain
。很像,但是目的、實現方式也有明顯的差異。pydash.chain
透過getattr
和getmethod
(他自己的實現),以及將一些函式包裝進module
,以實現類似能力。
我這裡單純希望Callable
可以接受一個函數物件去執行。