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' _______________________________________________ Python-3000 mailing list Python-3000@python.org http://mail.python.org/mailman/listinfo/python-3000 Unsubscribe: http://mail.python.org/mailman/options/python-3000/archive%40mail-archive.com