'Nested contexts trees for implementing nested scopes (static or dynamic)'
from collections import MutableMapping
from itertools import chain, imap
class Context(MutableMapping):
''' Nested contexts -- a chain of mapping objects.
c = Context() Create root context
d = c.new_child() Create nested child context. Inherit enable_nonlocal
e = c.new_child() Child of c, independent from d
e.root Root context -- like Python's globals()
e.map Current context dictionary -- like Python's locals()
e.parent Enclosing context chain -- like Python's nonlocals
d['x'] Get first key in the chain of contexts
d['x'] = 1 Set value in current context
del['x'] Delete from current context
list(d) All nested values
k in d Check all nested values
len(d) Number of nested values
d.items() All nested items
Mutations (such as sets and deletes) are restricted to the current context
when "enable_nonlocal" is set to False (the default). So c[k]=v will always
write to self.map, the current context.
But with "enable_nonlocal" set to True, variable in the enclosing contexts
can be mutated. For example, to implement writeable scopes for nonlocals:
nonlocals = c.parent.new_child(enable_nonlocal=True)
nonlocals['y'] = 10 # overwrite existing entry in a nested scope
To emulate Python's globals(), read and write from the the root context:
globals = c.root # look-up the outermost enclosing context
globals['x'] = 10 # assign directly to that context
To implement dynamic scoping (where functions can read their caller's
namespace), pass child contexts as an argument in a function call:
def f(ctx):
ctx.update(x=3, y=5)
g(ctx.new_child())
def g(ctx):
ctx['z'] = 8 # write to local context
print ctx['x'] * 10 + ctx['y'] # read from the caller's context
'''
def __init__(self, enable_nonlocal=False, parent=None):
'Create a new root context'
self.parent = parent
self.enable_nonlocal = enable_nonlocal
self.map = {}
self.maps = [self.map]
if parent is not None:
self.maps += parent.maps
def new_child(self, enable_nonlocal=None):
'Make a child context, inheriting enable_nonlocal unless specified'
enable_nonlocal = self.enable_nonlocal if enable_nonlocal is None else enable_nonlocal
return self.__class__(enable_nonlocal=enable_nonlocal, parent=self)
@property
def root(self):
'Return root context (highest level ancestor)'
return self if self.parent is None else self.parent.root
def __getitem__(self, key):
for m in self.maps:
if key in m:
break
return m[key]
def __setitem__(self, key, value):
if self.enable_nonlocal:
for m in self.maps:
if key in m:
m[key] = value
return
self.map[key] = value
def __delitem__(self, key):
if self.enable_nonlocal:
for m in self.maps:
if key in m:
del m[key]
return
del self.map[key]
def __len__(self, len=len, sum=sum, imap=imap):
return sum(imap(len, self.maps))
def __iter__(self, chain_from_iterable=chain.from_iterable):
return chain_from_iterable(self.maps)
def __contains__(self, key, any=any):
return any(key in m for m in self.maps)
def __repr__(self, repr=repr):
return ' -> '.join(imap(repr, self.maps))
if __name__ == '__main__':
c = Context()
c['a'] = 1
c['b'] = 2
d = c.new_child()
d['c'] = 3
print 'd: ', d
assert repr(d) == "{'c': 3} -> {'a': 1, 'b': 2}"
e = d.new_child()
e['d'] = 4
e['b'] = 5
print 'e: ', e
assert repr(e) == "{'b': 5, 'd': 4} -> {'c': 3} -> {'a': 1, 'b': 2}"
f = d.new_child(enable_nonlocal=True)
f['d'] = 4
f['b'] = 5
print 'f: ', f
assert repr(f) == "{'d': 4} -> {'c': 3} -> {'a': 1, 'b': 5}"
print len(f)
assert len(f) == 4
assert len(list(f)) == 4
assert all(k in f for k in f)
assert f.root == c
# dynanmic scoping example
def f(ctx):
print ctx['a'], 'f: reading "a" from the global context'
print 'f: setting "a" in the global context'
ctx['a'] *= 999
print 'f: reading "b" from globals and setting "c" in locals'
ctx['c'] = ctx['b'] * 50
print 'f: ', ctx
g(ctx.new_child())
print 'f: ', ctx
def g(ctx):
print 'g: setting "d" in the local context'
ctx['d'] = 44
print '''g: setting "c" in f's context'''
ctx['c'] = -1
print 'g: ', ctx
global_context = Context(enable_nonlocal=True)
global_context.update(a=10, b=20)
f(global_context.new_child())
Diff to Previous Revision
--- revision 1 2010-10-21 08:40:46
+++ revision 2 2010-10-25 02:13:37
@@ -29,7 +29,7 @@
can be mutated. For example, to implement writeable scopes for nonlocals:
nonlocals = c.parent.new_child(enable_nonlocal=True)
- nonlocals['y'] # overwrite existing entry in a nested scope
+ nonlocals['y'] = 10 # overwrite existing entry in a nested scope
To emulate Python's globals(), read and write from the the root context: