Issue 31968: exec(): method's default arguments from dict-inherited globals (original) (raw)

I'm using exec() to run code with globals object inherited from dict. The problem is overloaded getitem doesn't called to load default argument for class methods. Here the example. Let's assume we create some variable storage for code execution

class Env(dict): def init(self, external_storage): super().init() self._external_storage = external_storage

def __setitem__(self, key, value):
    print('__setitem__: {}'.format(key))
    self._external_storage[key] = value

def __getitem__(self, key):
    print('__getitem__: {}'.format(key))
    return self._external_storage[key]

storage = {} env = Env(storage) env['var'] = 2

exec(""" class A: def foo(self, x=var): print('foo(): {}'.format(x))

a = A() a.foo() """, env)

This code will fail with output: setitem: var Traceback (most recent call last): File "inheri-test.py", line 29, in """, env) File "", line 2, in File "", line 3, in A NameError: name 'var' is not defined

As far as I understand the problem is Python/ceval.c:2120. There is only PyDict_GetItem used to load variable from f_globals, instead of PyObject_GetItem in case of f_globals is not exact dict.

Yes, that's the way it works (and is intended to work, for performance reasons). The documentation on this could be improved...while it does say globals must be a dict and that locals can be any mapping object, it does it in a sentence that is a bit confusing in this context, and it doesn't make it clear that it has to be a "real" dict, not a subclass. (I wouldn't be surprised if that sentence was written back when you couldn't subclass dict.)