Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I access builtins if I use a custom dict as a function's globals?

I have a dict subclass like this:

class MyDict(dict):
    def __getitem__(self, name):
        return globals()[name]

This class can be used with eval and exec without issues:

>>> eval('bytearray', MyDict())
<class 'bytearray'>
>>> exec('print(bytearray)', MyDict())
<class 'bytearray'>

But if I instantiate a function object with the types.FunctionType constructor, the function can't access any builtins:

import types

func = lambda: bytearray
func_copy = types.FunctionType(func.__code__,
                              MyDict(),
                              func.__name__,
                              func.__defaults__,
                              func.__closure__)

print(func_copy())
# Traceback (most recent call last):
#   File "untitled.py", line 16, in <module>
#     print(func_copy())
#   File "untitled.py", line 8, in <lambda>
#     func = lambda: bytearray
# NameError: name 'bytearray' is not defined

Replacing MyDict() with globals() or dict(globals()) or event {'__builtins__': __builtins__} makes the code print <class 'bytearray'> as expected.

I don't understand where this exception is coming from. Can anyone explain this behavior? Why does it work with eval but not with a function object?

like image 789
Aran-Fey Avatar asked May 30 '18 12:05

Aran-Fey


1 Answers

Not a complete answer, but what seems to be happening is that CPython ignores the custom __getitem__ when it accesses the builtins. It seems to treat MyDict like a normal (not subclassed) dict. If the '__builtins__' key is actually present in the dict, then everything works correctly:

class MyDict(dict):
    def __getitem__(self, name):
        return globals()[name]


import types

globs = MyDict()
globs['__builtins__'] = __builtins__

func = lambda: bytearray
func_copy = types.FunctionType(func.__code__,
                              globs,
                              func.__name__,
                              func.__defaults__,
                              func.__closure__)

print(func_copy())
# output: <class 'bytearray'>

The question remains why this only happens with FunctionType, and not with eval and exec.

like image 80
Aran-Fey Avatar answered Oct 03 '22 19:10

Aran-Fey