简体   繁体   English

exec 和 eval 如何将 __builtins__ 添加到给定环境中?

[英]how do exec and eval add __builtins__ to a given environment?

I'm trying to understand how eval and exec treat the environment (globals and locals) that they are given, so I made a class "logdict" which behaves like a dict but logs most methods (__new__ is excluded):我试图了解 eval 和 exec 如何处理给定的环境(全局变量和本地变量),所以我制作了一个 class “logdict”,它的行为类似于字典,但记录了大多数方法(排除了 __new__ ):

from functools import wraps

class LogDict(dict):
    logs = {}
    def _make_wrapper(name):
        @wraps(getattr(dict, name))
        def wrapper(self, *args, **kwargs):
            LogDict.logs.setdefault(id(self), []).append({
                'name': name,
                'args': tuple(map(repr, args)),
                'kwargs': dict((key, repr(kwargs[key])) for key in kwargs)
                })
            return getattr(super(), name)(*args, **kwargs)
        return wrapper

    for attr in dir(dict):
        if callable(getattr(dict, attr)) and attr not in {'__new__',}:
            locals()[attr] = _make_wrapper(attr)

    def logrepr(self):
        return ''.join(
            "{fun}({rargs}{optsep}{rkwargs})\n".format(
                fun = logitem['name'],
                rargs = ', '.join(logitem['args']),
                optsep = ', ' if len(logitem['kwargs'])>0 else '',
                rkwargs = ', '.join('{} = {}'\
                 .format(key, logitem['kwargs'][key]) for key in logitem['kwargs'])
                )
            for logitem in LogDict.logs[id(self)])

as an example, this code:例如,这段代码:

d = LogDict()
d['1'] = 3
d['1'] += .5
print('1' in d)
print('log:')
print(d.logrepr())

produces this output:产生这个 output:

True
log:
__init__()
__setitem__('1', 3)
__getitem__('1')
__setitem__('1', 3.5)
__contains__('1')
__getattribute__('logrepr')

I tried feeding this to exec in order to understand how it was being used, but I can't see it accessing the dictionary beyond what makes sense:我试着将它提供给 exec 以了解它是如何被使用的,但我看不到它访问字典超出了意义:

print('\tTesting exec(smth, logdict):')
d = LogDict()
exec('print("this line is inside the exec statement")', d)
print('the log is:')
print(d.logrepr(), end='')
print('the env now contains:')
print(d)
    Testing exec(smth, logdict):
this line is inside the exec statement
the log is:
__init__()
__getitem__('print')
__getattribute__('logrepr')
the env now contains:
[a dictionary containing __builtins__]

so the exec function didn't call any of the methods I'm logging except __getitem__ to see if 'print' was in it (__getattribute__ is called later when I print the log);因此 exec function 没有调用我正在记录的任何方法,除了 __getitem__ 以查看其中是否包含“print”(稍后在我打印日志时调用 __getattribute__); how did it set the key '__builtins__' (or check that it wasn't already defined)?它是如何设置键“__builtins__”的(或检查它是否尚未定义)? Am I just missing the method it's using, or is it doing something more low-level?我只是错过了它正在使用的方法,还是它在做一些更底层的事情?

The exec function uses low-level dictionary functions in the Python C API to insert the __builtins__ module into the global namespace dictionary. exec function 使用 Python C API 中的低级字典函数将__builtins__模块插入到全局命名空间字典中。 You can see the call in the CPython source code .您可以在 CPython 源代码中看到调用。

Because the call is to low level dict API, it doesn't look in your class to find your overridden __setitem__ method, it just directly writes into the underlying dictionary storage.因为调用是对低级字典 API 的调用,所以它不会在您的 class 中查找您重写的__setitem__方法,它只是直接写入底层字典存储。 The exec function requires that the global namespace passed in to it is a dict (or a dict subclass, but not some other mapping type), so this is always safe, at least in terms of not crashing the interpreter. exec function 要求传递给它的全局命名空间是dict (或dict子类,但不是其他映射类型),因此这始终是安全的,至少在不使解释器崩溃方面是这样。 But it does bypass your logging.但它确实绕过了您的日志记录。

Unfortunately, I don't see any way to get logging added so that you can see __builtins__ get added to the global namespace.不幸的是,我没有看到任何方法来添加日志记录,以便您可以看到__builtins__被添加到全局命名空间。 That probably means your attempt to directly observe exec 's behavior is doomed.这可能意味着您尝试直接观察exec的行为是注定要失败的。 But perhaps reading the C source code is a suitable alternative, if you're just trying to understand what it does.但也许阅读 C 源代码是一个合适的选择,如果您只是想了解它的作用。 One of the perks of using an open source programming language is that you can just go look up how the interpreter is programmed when you have questions like this.使用开源编程语言的好处之一是,当您遇到此类问题时,只需 go 即可查看解释器的编程方式。 It does require reading C, rather than just Python, but the builtin_exec_impl function is straight forward enough (the actual code execution happens elsewhere and is surely much more complicated).它确实需要读取 C,而不仅仅是 Python,但是builtin_exec_impl function 足够直接(实际的代码执行发生在其他地方,而且肯定要复杂得多)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM