Nested Mapping

Steven D'Aprano steve at
Sat Oct 23 07:11:55 CEST 2010

On Thu, 21 Oct 2010 16:19:43 -0700, Raymond Hettinger wrote:

> I'm considering a nested mapping class for the collections module and
> would like to solicit feedback from people here on comp.lang.python:

Very nice!

The emulation of Python's nested scopes isn't perfect:

>>> c = Context()
>>> c['spam'] = 'global'
>>> c = c.new_child()
>>> c['spam'] = 'local'
>>> c['spam']
>>> del c['spam']
>>> c['spam']

So far so good -- that is exactly what I would expect. But:

>>> spam = "global"
>>> def f():
...     spam = "local"
...     print spam
...     del spam
...     print spam
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in f
UnboundLocalError: local variable 'spam' referenced before assignment

I'd call that a gotcha in CPython rather than a problem with the Context 
class. But it might be worth a note in the documentation somewhere.

> The API seeks to fully emulate regular dictionaries.

It doesn't appear to do so as yet. E.g. update is missing, although you 
refer to it in the comments.

I don't think that either iteration or len are right. I would expect that 
these should only count unique keys, not repeated keys. Given:

c = Context()
c['spam'] = 'global'
c = c.new_child()
c['spam'] = 'local'

I would expect that iteration should yield 'spam' once rather than twice, 
and c.items() should yield ('spam', 'local') rather than either:

('spam', 'local'), ('spam', 'global')
('spam', 'local'), ('spam', 'local')

For the rare(?) cases where users do want to see non-unique keys, it's 
trivial enough to get, given that Context.maps is public. Simple enough 
to do in place:

for key in chain.from_iterable(c.maps):

With the current API, getting unique keys is less simple:

seen = set()
for key in c:
    if key in seen: continue

I think the API should be based on unique keys, and leave getting non-
unique keys to the caller.

> I would appreciate any feedback on the API and on how well it fits with
> various use cases that you've found in the wild.

I can see myself using it in the future.

I'd like to see the constructor use the same signature as regular dicts. 
Instead of:

config = Context()
config.update({'spam':1, 'cheese':0}, parrot='sleeping')

I should be able to pass a dict or iterable of (key,item) pairs, plus 
keyword arguments, to initialise the newly created scope: 

config = Context({'spam':1, 'cheese':0}, parrot='sleeping')

And similarly for new_child().

I think this would be more useful to me than the current arguments taken 
by the constructor. I don't see myself needing enable_nonlocal often 
enough to want a shortcut for

>>> config = Context()
>>> config.enable_nonlocal = True

and as for parent, there doesn't seem to me to be any point to having it 
as an argument to the constructor. Instead of:

>>> top = Context()
>>> bottom = Context(parent=top)

you can say:

>>> top = Context()
>>> bottom = top.new_child()

Neither is particularly simpler than the other.


More information about the Python-list mailing list