When I pass a bona fide dict for locals
, exec()
does the correct thing and falls back to the globals dictionary for missing names. However, if I pass a LazyMap (dict-like object) as locals, accesses to globals raise an AttributeError from inside the lambda given to LazyMap.
#!/usr/bin/env python3
class LazyMap:
def __init__(self, keys, getter):
self._keys = keys
self._getter = getter
def keys(self):
return self._keys
def __getitem__(self, k):
return self._getter(k)
class TestObj:
def __init__(self):
self.field = 'foo'
obj = TestObj()
# this prints 'foo'
locs = dict((name, getattr(obj, name)) for name in dir(obj))
exec('print(field)', globals(), locs)
# this raises AttributeError
locs = LazyMap(dir(obj), lambda k,s=obj: getattr(s, k))
exec('print(field)', globals(), locs)
Why is an exception raised with LazyMap but not with a plain dict? How can I create a locals map that will only fetch/compute the value if accessed from the exec?
I guess that exec() first tries to getitem on the locals object, and if a
KeyError
is raised, it then falls back to getitem on the globals object. However, in the given code,getattr
will raise an AttributeError instead of a KeyError. Changing the passed lambda tovars(s)[k]
will cause a missing key to raise the correct KeyError instead.To summarize:
__getitem__
should raiseKeyError
on missing key, while__getattr__
should raiseAttributeError
.