posts - 403, comments - 310, trackbacks - 0, articles - 7
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          [zz]True closure in Python

          Posted on 2008-02-11 16:18 ZelluX 閱讀(813) 評論(0)  編輯  收藏 所屬分類: Scripting
          發現自己對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。
          不過我肯定不會讓它爛尾的。

           

          主站蜘蛛池模板: 江川县| 乐平市| 拉萨市| 绍兴县| 琼海市| 乌拉特中旗| 赤水市| 香河县| 珲春市| 南雄市| 漳州市| 虞城县| 鄂托克旗| 新营市| 九江县| 阿拉善左旗| 西宁市| 连江县| 阿拉尔市| 定陶县| 策勒县| 巢湖市| 马山县| 石城县| 永靖县| 昔阳县| 姜堰市| 昆明市| 天祝| 阿克| 台北县| 叶城县| 皋兰县| 金川县| 景德镇市| 丹阳市| 观塘区| 成都市| 阜阳市| 清水县| 巴南区|