[zz]True closure in Python
Posted on 2008-02-11 16:18 ZelluX 閱讀(817) 評(píng)論(0) 編輯 收藏 所屬分類: Scripting為什么水木上的帖子每行末尾都是用空格填充的,每次轉(zhuǎn)載還要先放到vim里面處理一下。。。
by ilovecpp
讓Python支持true closure有多難?
只需修改11行代碼。
如果你不知道什么是true closure,這里簡(jiǎn)單解釋一下。Python支持lexicalscope:
>>> def add_n(n):
... def f(m):
... return n+m
... return f
>>> add_2 = add_n(2)
>>> add_2(0)
2
>>> add_2(2)
4
f引用了外層函數(shù)add_n的局部變量n。有趣的是,f引用n的時(shí)候,add_n已經(jīng)結(jié)束,n似乎不存在了。f所以能正常工作,是因?yàn)閯?chuàng)建它的時(shí)候就把n作為f的上下文(closure)保存了下來(lái),并不隨add_n結(jié)束而消失。
但是,Python的lexical scope和Scheme/Smalltalk/Ruby還有一點(diǎn)區(qū)別:不能在內(nèi)層函數(shù)中rebind外層函數(shù)的局部變量。
>>> def f():
... def g():
... n=1
... n=0
... g()
... return n
...
>>> f()
0
這是因?yàn)镻ython沒(méi)有變量聲明, n=1 自動(dòng)使n成為g的局部變量,也就無(wú)法rebind f中的n了。可以說(shuō)Python的closure是只讀的。如果你聽(tīng)到有人說(shuō)"Python不支持true closure",就是指這個(gè)。其實(shí),Python VM能夠支持true closure。因?yàn)椋琍ython支持內(nèi)層函數(shù)看見(jiàn)外層函數(shù)的name rebinding:
>>> def f():
... def g():
... yield n
... yield n
... x = g()
... n = 0
... print x.next()
... n = 1
... print x.next()
...
>>> f()
0
1
對(duì)于Python的closure實(shí)現(xiàn)(flat closure),"外層函數(shù)rebind name"和"內(nèi)層函數(shù)rebind name"其實(shí)沒(méi)有區(qū)別。我們知道用global關(guān)鍵字可以rebind module scopename。如果增加一個(gè)類似的outer關(guān)鍵字,就可以支持rebind outer scope name。真正的限制是Guido不愿意為支持true closure增加關(guān)鍵字。
也可以不增加關(guān)鍵字,而是把global n的語(yǔ)義改為"如果outer scope定義了n,rebind outer scope n;否則rebind module scope n"。簡(jiǎn)單起見(jiàn),我沒(méi)有修改Python的built-in compiler,而是修改了compiler module(用Python實(shí)現(xiàn)的Python compiler)。你只需把下面這個(gè)patch打到compiler/symbols.py(Python 2.5.1)就可以體驗(yàn)true closure了:
C:\Python\Lib>diff -u compiler/symbols.py.orig compiler/symbols.py
--- compiler/symbols.py.orig Thu Aug 17 10:28:56 2006
+++ compiler/symbols.py Mon Feb 11 12:03:01 2008
@@ -21,6 +21,7 @@
self.params = {}
self.frees = {}
self.cells = {}
+ self.outers = {}
self.children = []
# nested is true if the class could contain free variables,
# i.e. if it is nested within another function.
@@ -54,8 +55,10 @@
if self.params.has_key(name):
raise SyntaxError, "%s in %s is global and parameter" % \
(name, self.name)
- self.globals[name] = 1
- self.module.add_def(name)
+ if self.nested:
+ self.outers[name] = 1
+ else:
+ self.globals[name] = 1
def add_param(self, name):
name = self.mangle(name)
@@ -90,6 +93,8 @@
"""
if self.globals.has_key(name):
return SC_GLOBAL
+ if self.outers.has_key(name):
+ return SC_FREE
if self.cells.has_key(name):
return SC_CELL
if self.defs.has_key(name):
@@ -107,6 +112,7 @@
return ()
free = {}
free.update(self.frees)
+ free.update(self.outers)
for name in self.uses.keys():
if not (self.defs.has_key(name) or
self.globals.has_key(name)):
@@ -134,6 +140,9 @@
free.
"""
self.globals[name] = 1
+ if self.outers.has_key(name):
+ self.module.add_def(name)
+ del self.outers[name]
if self.frees.has_key(name):
del self.frees[name]
for child in self.children:
因?yàn)槲覀儧](méi)有修改built-in compiler,所以程序要寫在字符串里,用compiler.compile編譯,用exec執(zhí)行:
>>> from compiler import compile
>>> s = '''
... def counter():
... n = 0
... def inc():
... global n
... n += 1
... def dec():
... global n
... n -= 1
... def get():
... return n
... return inc, dec, get
... '''
>>> exec compile(s, '', 'exec')
>>> inc, dec, get = counter()
>>> get()
0
>>> inc()
>>> get()
1
>>> dec()
>>> get()
0
后記
1 搞這個(gè)東西的緣起是Selfless Python(http://www.voidspace.org.uk/python/weblog/arch_d7_2006_12_16.shtml#e583)。很有趣的bytecode hack,給一個(gè)類中的所有函數(shù)補(bǔ)上self參數(shù)。既然PythonVM支持true closure,能不能用類似的手法讓Python支持true closure呢?不過(guò)很快就明白這個(gè)在bytecode層面不好弄,還是得修改編譯器。不過(guò)改起來(lái)還真是出乎意料地簡(jiǎn)單。
2 Guido早已明確表示不能改變global的語(yǔ)義(因?yàn)闀?huì)影響現(xiàn)有代碼),所以這個(gè)只是玩玩而已,不用指望成為現(xiàn)實(shí)。當(dāng)然你可以只發(fā)布bytecode,大概還能把反編譯器搞掛掉。:-)
3 我可以理解Guido的決定。除非你之前一直在用Scheme,否則我覺(jué)得像上面counter例子那種一組共享狀態(tài)的函數(shù)還是寫成class為好,至少共享狀態(tài)是什么一目了然。Lexical scope太implicit,用在開(kāi)頭add_n那種地方挺方便,再?gòu)?fù)雜就不好了。
又:很抱歉"幕后的故事"拖了這么久。寫起來(lái)才發(fā)現(xiàn)自己還是不懂descriptor。
不過(guò)我肯定不會(huì)讓它爛尾的。