發現自己對Python的語法的興趣遠比對使用Python本身的興趣濃厚得多
為什么水木上的帖子每行末尾都是用空格填充的,每次轉載還要先放到vim里面處理一下。。。
by ilovecpp
讓Python支持true closure有多難?
只需修改11行代碼。
如果你不知道什么是true closure,這里簡單解釋一下。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引用了外層函數add_n的局部變量n。有趣的是,f引用n的時候,add_n已經結束,n似乎不存在了。f所以能正常工作,是因為創建它的時候就把n作為f的上下文(closure)保存了下來,并不隨add_n結束而消失。
但是,Python的lexical scope和Scheme/Smalltalk/Ruby還有一點區別:不能在內層函數中rebind外層函數的局部變量。
>>> def f():
... def g():
... n=1
... n=0
... g()
... return n
...
>>> f()
0
這是因為Python沒有變量聲明, n=1 自動使n成為g的局部變量,也就無法rebind f中的n了。可以說Python的closure是只讀的。如果你聽到有人說"Python不支持true closure",就是指這個。其實,Python VM能夠支持true closure。因為,Python支持內層函數看見外層函數的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
對于Python的closure實現(flat closure),"外層函數rebind name"和"內層函數rebind name"其實沒有區別。我們知道用global關鍵字可以rebind module scopename。如果增加一個類似的outer關鍵字,就可以支持rebind outer scope name。真正的限制是Guido不愿意為支持true closure增加關鍵字。
也可以不增加關鍵字,而是把global n的語義改為"如果outer scope定義了n,rebind outer scope n;否則rebind module scope n"。簡單起見,我沒有修改Python的built-in compiler,而是修改了compiler module(用Python實現的Python compiler)。你只需把下面這個patch打到compiler/symbols.py(Python 2.5.1)就可以體驗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:
因為我們沒有修改built-in compiler,所以程序要寫在字符串里,用compiler.compile編譯,用exec執行:
>>> 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 搞這個東西的緣起是Selfless Python(http://www.voidspace.org.uk/python/weblog/arch_d7_2006_12_16.shtml#e583)。很有趣的bytecode hack,給一個類中的所有函數補上self參數。既然PythonVM支持true closure,能不能用類似的手法讓Python支持true closure呢?不過很快就明白這個在bytecode層面不好弄,還是得修改編譯器。不過改起來還真是出乎意料地簡單。
2 Guido早已明確表示不能改變global的語義(因為會影響現有代碼),所以這個只是玩玩而已,不用指望成為現實。當然你可以只發布bytecode,大概還能把反編譯器搞掛掉。:-)
3 我可以理解Guido的決定。除非你之前一直在用Scheme,否則我覺得像上面counter例子那種一組共享狀態的函數還是寫成class為好,至少共享狀態是什么一目了然。Lexical scope太implicit,用在開頭add_n那種地方挺方便,再復雜就不好了。
又:很抱歉"幕后的故事"拖了這么久。寫起來才發現自己還是不懂descriptor。
不過我肯定不會讓它爛尾的。