我最近着迷於Python。以前,我主要使用C++和Matlab編程數值和數據分析代碼。我看到很多關於Python,Ruby和閉包的討論。幾乎所有的例子都是這樣的:關閉:什麼是一個好的用例示例?爲什麼不是一個函子?這是值得的否定嗎?
>>> def makeAdder(y):
... def myAdder(x):
... return x + y
... return myAdder
...
>>> f = makeAdder(10)
>>> f(5)
15
我知道這在某種意義上是有用的。然而,現實的是,在這樣的情況下的行爲(「只讀」的情況下,因爲它是)可以容易地通過一個對象(算符)進行仿真:
>>> class MyAdder(object):
... def __init__(self,y):
... self.y = y
... def __call__(self,x):
... return self.y + x
...
>>> f = MyAdder(5)
>>> f(10)
15
對象不佔用基本上更多的空間來代碼,而且它更加通用。跟蹤和調試後續代碼也容易得多。
在這種情況下,我們只從非局部變量讀取。但是我們也可以編寫它:在Ruby中,當然,在Python中使用nonlocal關鍵字。該對象當然也支持這一點。但是,通過這個對象,您可以將數據捆綁在一起,以便您確切知道發生了什麼。閉包可能會以完全不透明的方式攜帶變量,這可能導致代碼難以調試。這是一個非常奇怪的例子:
irb(main):001:0> def new_counter
irb(main):002:1> x = 0
irb(main):003:1> lambda { x +=1 }
irb(main):004:1> end
=> nil
irb(main):005:0> counter_a = new_counter
=> #<Proc:[email protected](irb):3>
irb(main):006:0> counter_a.call
=> 1
irb(main):007:0> counter_a.call
=> 2
至少對我而言,這種行爲是不直觀的。它也有可能導致內存泄漏。這給你一個巨大的繩子掛上自己。同樣,在Ruby中,你不需要明確地啓用它(與Python不同),而且在Ruby中,所有的主代碼都可以訪問所有的代碼。如果外部變量由於處於閉包而發生變化,如果你傳遞閉包,你可以有一個變量無限期地遠離它所在的地方。與始終安全地攜帶其數據的對象相反。
爲什麼你會聽到很多關於閉包有多好的說法,以及它們應該如何包含在Java中,當它們不完全在Python中時它是如何被吸引的?爲什麼不使用函子?或者重構代碼以避免它們的危險程度如何?只是爲了澄清,我不是那些在OO類型中起泡的人之一。我是低估了他們的使用,誇大了他們的危險,還是兩者兼而有之?
編輯:也許我應該區分三件事情:只讀一次(這是我的例子顯示,幾乎每個人都討論)的閉包,一般讀取的閉包和寫入的閉包。如果使用外部函數的局部變量在另一個函數內部定義一個函數,那麼幾乎沒有機會回到你身邊。該空間中的變量無法以我能想到的方式訪問,因此您無法更改它。這是非常安全的,並且是一種方便的(可能比函子更多的)生成函數的方式。另一方面,如果您在類方法內部或在主線程內部創建閉包,則每次調用變量時都會讀取可從其他位置訪問的變量。所以它可以改變。我認爲這很危險,因爲封閉的over變量沒有出現在函數頭部。你可以在你的代碼的第1頁上說一個關閉主線程變量x的長關閉,然後爲了不相關的原因修改x。然後重新使用閉包,並得到你不明白的怪異行爲,這可能很難調試。 如果你真的寫封閉變量,那麼就像我用Ruby的例子表明你真的有可能弄得一團糟,並導致意外的行爲。
Edit2:我給出了第三個用法的閉包中的奇怪行爲的例子,寫入非局部變量。下面是從第二利用離奇的(不是那麼糟糕)行爲的一個例子(限定範圍倒閉,他們的封閉過的變量可以被修改):
>>> fs = [(lambda n: i + n) for i in range(10)]
>>> fs[4](5)
14
我不知道紅寶石,所以我不知道,如果你的內存要求泄漏是現實的。這不是Python中的問題,也許你可以澄清這一點? –
同意,特別是我不知道哪裏*非常危險*來自。 –
我認爲它主要歸結爲Python不是C++,Ruby不是C++,JavaScript不是C++,Perl不是C++,...如果你嘗試在任何地方使用函子,那麼人們會想知道爲什麼你要寫Python中的C++。我可能會說,函子是沒有GC和閉包的語言的混雜或實現細節,更多動態語言(通常)不需要的混合語言。而當你不需要所有這些機器時,爲什麼要定義一個全新的課程? –