You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

exec与eval向传入的执行环境添加__builtins__的机制是什么?

exec/eval执行环境调用行为问题

问题背景

为了理解eval和exec对传入执行环境(globals、locals参数)的处理逻辑,实现了一个可以记录方法调用的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')

异常现象

将LogDict实例作为执行环境传入exec观察调用行为时,日志中仅能看到查找print的__getitem__调用,但最终字典中出现了__builtins__键,没有任何相关操作被记录:
测试代码:

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__]

问题解答

你没有遗漏方法的记录,__builtins__的检查和注入是CPython解释器通过底层C API直接操作字典内部存储完成的,没有走Python层面重载的魔术方法,所以不会被LogDict捕获。
具体逻辑如下:

  • 当你给exec传入自定义globals参数时,解释器会首先检查该字典中是否存在__builtins__键,这个检查调用的是C层面的PyDict_GetItem接口,直接访问字典的底层哈希表,不会触发Python类中重载的__getitem__方法。
  • 如果未检测到__builtins__,解释器会通过C层面的PyDict_SetItem接口直接将内置命名空间注入到globals的底层存储中,同样不会触发Python类中重载的__setitem__方法。
  • 这种实现是解释器的性能优化手段,普通场景下使用原生dict作为执行环境时不会感知到差异,只有当你继承dict重载了相关魔术方法时才会出现方法调用未被捕获的情况。

内容的提问来源于stack exchange,提问作者sortai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 23:15:01