[Python-3000] Draft PEP for outer scopes

Michael Spencer mahs at telcopartners.com
Thu Nov 2 00:28:23 CET 2006


Ka-Ping Yee wrote:
> Hi folks,
> 
> I have finally completed a draft of a PEP on rebinding of names
> in outer scopes.  I've tried to go back and gather all of the
> (amazingly numerous) proposals -- if i've forgotten or misattributed
> any, let me know and i'll be happy to correct them.
> 
> I look forward to your thoughts on it:
> 
>     http://zesty.ca/python/pep-3104.txt
> 
> (Could i get an official PEP number, please?)
> 
> 
> -- ?!ng
Looks good.  I tried an experimental implementation using the compiler2 package. 
  (http://groups.google.com/group/comp.lang.python/msg/f10db8a1ca2047b4)

compiler2 doesn't do parsing, so instead of `nonlocal a,b` I abuse existing 
syntax and write `global __nonlocal__,a,b`

I did not try to implement `nonlocal a=3` form, since the existing global syntax 
does not allow it.  If it's going to be legal, then will global be similarly 
adjusted?

I assume nonlocal statements should not nest the way global does i.e., a 
nonlocal statement in one scope has no affect on assignment in an *enclosed* scope.

Michael

Here it is:

"""Experimental implementation of 'PEP 3104', using compiler2.
Requires:
   Python 2.5
   compiler2 from http://svn.brownspencer.com/pycompiler/branches/new_ast/
   See readme.txt for installation
   This file also available from:
    http://svn.brownspencer.com/pycompiler/branches/new_ast/sandbox/nonlocal.py
"""

from compiler2.pyassem import (CompilerUnit, FunctionMixin, ClassMixin,
     ModuleMixin, SymbolVisitor, CodeGenerator, OrderedSet, SYMBOLS, parse)

from _ast import AST, stmt, Global
import _ast

class NonLocal(stmt):
     """A new node type to represent the PEP3104 nonlocal statement"""
     _fields = ('names',)
     _attributes = ['lineno', 'col_offset']

# push NonLocal into _ast to satisfy SymbolVisitor
_ast.NonLocal = NonLocal


def convert_nonlocal(tree):
     """We have no syntax support for 'nonlocal' keyword.  So, instead,
     use global __nonlocal__, target[s].  This conversion function then
     turns the Global nodes into NonLocal, and removes the __nonlocal__
     flag"""

     stack = [tree]
     while stack:
         node = stack.pop()
         if isinstance(node, Global):
             if node.names[0] == "__nonlocal__":
                 node.__class__ = NonLocal
                 del node.names[0]
         else:
             for attr in node.__dict__.values():
                 if type(attr) is list:
                     stack.extend(elt for elt in attr if isinstance(elt, AST))
                 elif isinstance(attr, AST):
                     stack.append(attr)

class MySymbolVisitor(SymbolVisitor):

     def visitNonLocal(self, node, cu):
         for name in node.names:
             cu.add_free(name)


class MyCompilerUnit(CompilerUnit):

     def __init__(self, *args):
        CompilerUnit.__init__(self, *args)
        self.frees = set() # track names marked explicitly free (via nonlocal)

     # adjust add_def to ignore frees, like globals
     def add_def(self, name):
         m_name = self.mangle(name)
         if m_name not in self.globals and m_name not in self.frees:
             if m_name in self.defs:
                 self.redefs.add(m_name)
             self.defs.add(m_name)
             self.symbols.add(m_name)
             self.uses.discard(m_name)

     # new method - called by MySymbolVisitor.visitNonLocal
     def add_free(self, name):
         """Mark a name as explicitly free, i.e., nonlocal"""
         m_name = self.mangle(name)
         if m_name in self.uses or m_name in self.defs:
             pass # Warn about nonlocal following def/use
         elif m_name in self.params:
             raise SyntaxError, "Parameter can't be declared nonlocal"
         elif m_name in self.globals:
             raise SyntaxError, "Name can't be declared global and nonlocal"
         self.frees.add(m_name)
         self.symbols.add(m_name)

     def _get_frees(self, child_frees, outer_defs):
         """Helper for finish_symbol_analysis"""

         # only change from base method is to include self.frees
         self_frees = (self.frees | self.uses | child_frees) & outer_defs
         if self_frees:
             symbols=self.symbols
             fv = [n for n in symbols if n in self_frees]
             fv += (n for n in self_frees if n not in symbols)
             self.freevars = tuple(fv)
             self.closure.update(fv)
         return self_frees

     # Override required to use re-defined FunctionUnit
     def new_child_function(self, node, name, firstlineno):
         """Create a new function unit as a child of the current one."""
         return FunctionUnit(node, self.filename, name, firstlineno, self)

     # Override required to use re-defined ClassUnit
     def new_child_class(self, node, name, firstlineno):
         """Create a new function unit as a child of the current one."""
         return ClassUnit(node, self.filename, name, firstlineno, self)



class FunctionUnit(FunctionMixin, MyCompilerUnit):
     pass

class ClassUnit(ClassMixin, MyCompilerUnit):
     pass

class ModuleUnit(ModuleMixin, MyCompilerUnit):

     # Override to use our re-defined SymbolVisitor
     def make_scopes(self, visitor = MySymbolVisitor):
         s = visitor()
         s.visit(self.node, self)


# Adjust pycompile to take non_local flag
def pycompile(source, filename, mode, flags=None, dont_inherit=None, 
non_local=False):
     """Replacement for builtin compile() function"""
     tree = parse(source, filename, mode)
     if non_local:
         convert_nonlocal(tree)
     if mode == "exec":
         gen = ModuleUnit(tree, filename)
     else:
         raise ValueError("compile() 3rd arg must be 'exec' ["
                          "'eval' and 'single' not yet Implemented]")
     return gen.compile()


test_src = """\
# Note the behavior of the a=3 assignment depends on whether nonlocal
# is enabled.  If so, it rebinds f's cellvar.  If not, it rebinds
# global a

a=1
def f():
     a=2
     def g():
         global __nonlocal__, a # means nonlocal a
         a=3
     g()
     return a
f_a = f()
"""
"""
Examples and test:

  # using the nonlocal feature
  >>> nsp = {}
  >>> exec pycompile(test_src, "string", "exec", non_local = True) in nsp
  >>> assert nsp['a'] == 1
  >>> assert nsp['f_a'] == 3

  # compare with compiler2, nonlocal not active
  >>> nsp = {}
  >>> exec pycompile(test_src, "string", "exec", non_local = False) in nsp
  >>> assert nsp['a'] == 3
  >>> assert nsp['f_a'] == 2

  # compare with __builtin__.compile
  >>> nsp = {}
  >>> exec compile(test_src, "string", "exec") in nsp
  >>> assert nsp['a'] == 3
  >>> assert nsp['f_a'] == 2
  >>>
"""

def test():
     import doctest
     doctest.testmod()

if __name__ == "__main__":
     test()
     print 'ok'



More information about the Python-3000 mailing list