Easy to use chain of dictionaries for crafting nested scopes or for a tree of scopes. Useful for analyzing AST nodes, XML nodes or other structures with multiple scopes. Can emulate various chaining styles including static/lexical scoping, dynamic scoping and Python's own globals(), locals(), nested scopes, and writeable nonlocals. Can also model Python's inheritance chains: instance dictionary, class dictionary, and base classes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 | 'Nested contexts trees for implementing nested scopes (static or dynamic)'fromcollectionsimportMutableMappingfromitertoolsimportchain,imapclassContext(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=parentself.enable_nonlocal=enable_nonlocalself.map={}self.maps=[self.map]ifparentisnotNone:self.maps+=parent.mapsdefnew_child(self,enable_nonlocal=None):'Make a child context, inheriting enable_nonlocal unless specified'enable_nonlocal=self.enable_nonlocalifenable_nonlocalisNoneelseenable_nonlocalreturnself.__class__(enable_nonlocal=enable_nonlocal,parent=self)@propertydefroot(self):'Return root context (highest level ancestor)'returnselfifself.parentisNoneelseself.parent.rootdef__getitem__(self,key):forminself.maps:ifkeyinm:breakreturnm[key]def__setitem__(self,key,value):ifself.enable_nonlocal:forminself.maps:ifkeyinm:m[key]=valuereturnself.map[key]=valuedef__delitem__(self,key):ifself.enable_nonlocal:forminself.maps:ifkeyinm:delm[key]returndelself.map[key]def__len__(self,len=len,sum=sum,imap=imap):returnsum(imap(len,self.maps))def__iter__(self,chain_from_iterable=chain.from_iterable):returnchain_from_iterable(self.maps)def__contains__(self,key,any=any):returnany(keyinmforminself.maps)def__repr__(self,repr=repr):return' -> '.join(imap(repr,self.maps))if__name__=='__main__':c=Context()c['a']=1c['b']=2d=c.new_child()d['c']=3print'd: ',dassertrepr(d)=="{'c': 3} -> {'a': 1, 'b': 2}"e=d.new_child()e['d']=4e['b']=5print'e: ',eassertrepr(e)=="{'b': 5, 'd': 4} -> {'c': 3} -> {'a': 1, 'b': 2}"f=d.new_child(enable_nonlocal=True)f['d']=4f['b']=5print'f: ',fassertrepr(f)=="{'d': 4} -> {'c': 3} -> {'a': 1, 'b': 5}"printlen(f)assertlen(f)==4assertlen(list(f))==4assertall(kinfforkinf)assertf.root==c# dynanmic scoping exampledeff(ctx):printctx['a'],'f: reading "a" from the global context'print'f: setting "a" in the global context'ctx['a']*=999print'f: reading "b" from globals and setting "c" in locals'ctx['c']=ctx['b']*50print'f: ',ctxg(ctx.new_child())print'f: ',ctxdefg(ctx):print'g: setting "d" in the local context'ctx['d']=44print'''g: setting "c" in f's context'''ctx['c']=-1print'g: ',ctxglobal_context=Context(enable_nonlocal=True)global_context.update(a=10,b=20)f(global_context.new_child()) |
Easy to use:
>>> d = Context() # Create a new context>>> d['k'] = 1 # Store a key/value just like a dict>>> e = d.new_child() # Create a child context>>> e['q'] = 2 # Store an item in the child context>>> e['k'] # Look-up a key in the child, then parent1>>> e # Display the dictionary chain{'q': 2} -> {'k': 1}That's cool.
Nice!
Really cool recipe.
Perhaps a useful enhancement would be the possibility of passing in a dict or extra_context mapping when calling Context() or c.new_child().
I think__setitem__ and__delitem__ should raise KeyError if the key is not found.Also IMO__len__ since it'll count keys shared by parent and child twice. Should be something like:
return len(set(chain.from_iterable(m.iterkeys() for m in self.maps)))The above should read:... __len__ is wrong since ...
| Created byRaymond HettingeronThu, 21 Oct 2010(MIT) |
| ◄ | Python recipes (4591) | ► |
| ◄ | Raymond Hettinger's recipes (97) | ► |
| ◄ | HongxuChen's Fav (39) | ► |
Privacy Policy |Contact Us |Support
© 2024 ActiveState Software Inc. All rights reserved. ActiveState®, Komodo®, ActiveState Perl Dev Kit®, ActiveState Tcl Dev Kit®, ActivePerl®, ActivePython®, and ActiveTcl® are registered trademarks of ActiveState. All other marks are property of their respective owners.