Why does Python3 exec() raise an AttributeError when a variable is not in locals but is in gl

385 views Asked by At

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?

1

There are 1 answers

0
saul On

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 to vars(s)[k] will cause a missing key to raise the correct KeyError instead.

To summarize: __getitem__ should raise KeyError on missing key, while __getattr__ should raise AttributeError.