[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