exec 和 eval 如何将 __builtins__ 添加到给定环境?
how do exec and eval add __builtins__ to a given environment?
我试图了解 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)])
例如,此代码:
d = LogDict()
d['1'] = 3
d['1'] += .5
print('1' in d)
print('log:')
print(d.logrepr())
产生这个输出:
True
log:
__init__()
__setitem__('1', 3)
__getitem__('1')
__setitem__('1', 3.5)
__contains__('1')
__getattribute__('logrepr')
我试着将它提供给 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__]
所以 exec 函数没有调用我正在记录的任何方法,除了 __getitem__ 以查看 'print' 是否在其中(__getattribute__ 稍后在我打印时调用日志);它是如何设置键“__builtins__”的(或检查它是否尚未定义)?我只是错过了它正在使用的方法,还是它在做一些更底层的事情?
exec
函数使用 Python C API 中的低级字典函数将 __builtins__
模块插入到全局命名空间字典中。可以看到调用in the CPython source code.
因为调用是低级字典 API,它不会在您的 class 中查找您重写的 __setitem__
方法,它只是直接写入底层字典贮存。 exec
函数要求传递给它的全局命名空间是 dict
(或 dict
subclass,但不是其他映射类型),所以这总是安全,至少在不使解释器崩溃方面是这样。但它确实会绕过您的日志记录。
不幸的是,我看不到任何添加日志记录的方法,因此您可以看到 __builtins__
被添加到全局命名空间。这可能意味着你试图直接观察 exec
的行为是注定要失败的。但也许阅读 C 源代码是一个合适的选择,如果您只是想了解它的作用。使用开源编程语言的好处之一是当你有这样的问题时,你可以去看看解释器是如何编程的。它确实需要阅读 C,而不仅仅是 Python,但 builtin_exec_impl
函数足够简单(实际代码执行发生在其他地方,并且肯定要复杂得多)。
我试图了解 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)])
例如,此代码:
d = LogDict()
d['1'] = 3
d['1'] += .5
print('1' in d)
print('log:')
print(d.logrepr())
产生这个输出:
True
log:
__init__()
__setitem__('1', 3)
__getitem__('1')
__setitem__('1', 3.5)
__contains__('1')
__getattribute__('logrepr')
我试着将它提供给 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__]
所以 exec 函数没有调用我正在记录的任何方法,除了 __getitem__ 以查看 'print' 是否在其中(__getattribute__ 稍后在我打印时调用日志);它是如何设置键“__builtins__”的(或检查它是否尚未定义)?我只是错过了它正在使用的方法,还是它在做一些更底层的事情?
exec
函数使用 Python C API 中的低级字典函数将 __builtins__
模块插入到全局命名空间字典中。可以看到调用in the CPython source code.
因为调用是低级字典 API,它不会在您的 class 中查找您重写的 __setitem__
方法,它只是直接写入底层字典贮存。 exec
函数要求传递给它的全局命名空间是 dict
(或 dict
subclass,但不是其他映射类型),所以这总是安全,至少在不使解释器崩溃方面是这样。但它确实会绕过您的日志记录。
不幸的是,我看不到任何添加日志记录的方法,因此您可以看到 __builtins__
被添加到全局命名空间。这可能意味着你试图直接观察 exec
的行为是注定要失败的。但也许阅读 C 源代码是一个合适的选择,如果您只是想了解它的作用。使用开源编程语言的好处之一是当你有这样的问题时,你可以去看看解释器是如何编程的。它确实需要阅读 C,而不仅仅是 Python,但 builtin_exec_impl
函数足够简单(实际代码执行发生在其他地方,并且肯定要复杂得多)。