2015-12-16 50 views
11

所以我很好奇下面樣式問題的更有經驗的python程序員的看法。假設我正在構建一個函數,它將逐行遍歷一個熊貓數據框或任何類似的用例,其中一個函數需要訪問其以前的狀態。似乎有至少四種方式在Python實現這一點:python中的習語:closure vs functor vs object

1)瓶蓋:

def outer(): 
    previous_state = None 
    def inner(current_state) : 
     nonlocal previous_state 
     #do something 
     previous_state=current_state 
     return something 

所以,如果你來自一個JavaScript的背景,這將毫無疑問很自然的你。在python中它也很自然,直到你需要訪問封閉範圍,當你最終完成像inner.__code__.co_freevars這樣的操作時,它會給你封閉變量的名稱作爲一個元組,並找到一個元組的索引你想要的,然後去inner.__closure__[index].cell_contents來獲得它的價值。不完全優雅,但我想這一點通常是隱藏範圍,所以它很有意義,應該很難達到。另一方面,它也感到有點奇怪,python使封閉函數成爲私有的,因爲它幾乎消除了與OOP語言相比私有變量的其他方式。

2)函子

def outer(): 
    def inner(current_state): 
     #do something 
     inner.previous_state=current_state 
     return something 
    ret = inner 
    ret.previous_state=None 
    return ret 

該「打開封閉的」,因爲現在封閉狀態是作爲函數的屬性完全可見。這是有效的,因爲函數實際上只是僞裝的對象。我傾向於這是最pythonic。其清晰,簡潔和可讀性強。

3)對象 這可能是最熟悉的OOP編程

class Calculator(Object) : 
    def __init__(self): 
     self.previous_state=None 

    def do_something(self, current_state) : 
     #do_something 
     self.previous_state = current_state 
     return something 

這裏最大的con是,你往往有很多的類定義的結束了。在像Java這樣的完全OOP語言中,這很好,您可以使用接口等來管理這個語言,但是在鴨子語言中有許多簡單的類只是爲了傳遞需要一些狀態的函數似乎有點奇怪。

4)全局 - 我不會證明這一點,我特別希望避免污染全局命名空間

5)裝飾 - 這是一個弧線球的一點點,但你可以用裝飾來存儲部分狀態信息。

@outer 
def inner(previous_state, current_state): 
    #do something 
    return something 

def outer(inner) : 
    def wrapper(current_state) : 
     result = inner(wrapper.previous_state, current_state) 
     wrapper.previous_state = current_state 
     return result 
    ret = wrapper 
    ret.previous_state=None 
    return result 

這種語法是最熟悉的我,但如果我現在請

func = inner 

我真正得到

func = outer(inner) 

,然後重複調用func()行爲就像函子例。我其實最恨這種方式。在我看來,有一個非常不透明的語法,因爲很多時候調用inner(current_state)會給你相同的結果,或者它每次都會給你一個新裝飾的函數,所以它看起來很糟糕以這種方式使裝飾器將狀態添加到函數中。

那麼哪種方法是正確的?我在這裏錯過了什麼優點和缺點?

+1

我不是很追隨爲什麼你認爲你需要通過'inner .__ closure__'來訪問封閉變量;名稱只會在該結構中結束,因爲您主動在內部函數* already *中使用它們。 '__closure__'結構真的是一個內部實現細節。 –

+0

我會讓人們開發這個,但從經驗來看,python開發人員傾向於將類用於這種場景。或者當你不需要完全清晰的時候裝飾器,並且用'@'選擇好的語法。你不想使用'nonlocal'或'global'關鍵字。仿函數經常被關閉。 – Cyrbil

+0

封閉的名字在這方面與當地人沒有什麼不同;你通常不會閱讀他們的當地人的功能,你爲什麼要在封閉的情況下做同樣的事情?如果您需要將該狀態暴露給函數以外的地方,請使用函子或類的方法。 –

回答

3

所以對此的正確答案是可調用對象,它基本上替代了python中閉包的習慣用法。

所以工作關閉選項3以上的變化:

class Calculator(Object) : 
    def __init__(self): 
     self.previous_state=None 

    def do_something(self, current_state) : 
     #do_something 
     self.previous_state = current_state 
     return something 

class Calculator(Object) : 
    def __init__(self): 
     self.previous_state=None 

    def __call__(self, current_state) : 
     #do_something 
     self.previous_state = current_state 
     return something 

,現在你可以這樣調用它的功能。所以

func = Calculator(): 
for x in list: 
    func(x) 
2

您可以定義一個生成器,它是一個協處理的受限形式。

def make_gen(): 
    previous_state = None 
    for row in rows: 
     # do something 
     previous_state = current_state 
     yield something 

thing = make_gen() 
for item in thing: 
    # Each iteration, item is a different value 
    # "returned" by the yield statement in the generator 

而不是調用thing(代替你的內部函數)反反覆覆,你迭代它(這基本上是與調用next(thing)反覆)的。

該狀態完全包含在發生器的主體內。

如果您不想實際迭代它,仍可以通過明確調用next來選擇性地「重新輸入」協處理。

thing = make_gen() 
first_item = next(thing) 
# do some stuff 
second_item = next(thing) 
# do more stuff 
third_item = next(thing) 
fourth_item = next(thing) 
# etc 
+0

從問題中不清楚OP的代碼中將如何使用它。我懷疑數據框中的行將作爲參數傳遞給'make_gen'(目前,我沒有參數,但循環中的自由變量'rows'應該來自數據框)。 – chepner