为什么删除名为 __builtins__ 的全局变量只会阻止 REPL 访问内置函数?
Why does deleting a global variable named __builtins__ prevent only the REPL from accessing builtins?
我有一个包含以下内容的 python 脚本:
# foo.py
__builtins__ = 3
del __builtins__
print(int) # <- this still works
奇怪的是,使用 -i
标志执行此脚本会阻止 仅 REPL 访问内置函数:
aran-fey@starlight ~> python3 -i foo.py
<class 'int'>
>>> print(int)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'print' is not defined
为什么脚本可以访问内置函数,而 REPL 不能?
执行上下文不同。在 REPL 中,我们逐行工作 (Read, Eval, Print , Loop), 这允许在每个步骤之间更改全局执行范围的机会。但是执行模块的运行时是加载模块代码,然后在一个范围内执行。
在 CPython 中,通过在全局命名空间中查找名称 __builtins__
来找到与代码块执行相关联的内置命名空间;这应该绑定到字典或模块(在后一种情况下使用模块的字典)。当在__main__
模块中时,__builtins__
是内置模块builtins
,否则__builtins__
绑定到builtins
模块本身的字典。在您问题的两种情况下,我们都在 __main__
模块中。
重要的是 CPython 仅在开始执行您的代码之前 一次 查找内置函数。在 REPL 中,每次执行新语句时都会发生这种情况。但是当执行 python 脚本时,脚本的全部内容是一个单元。这就是为什么删除脚本中间的内置函数没有效果。
为了在 REPL 中更紧密地复制该上下文,您不会逐行输入模块的代码,而是使用复合语句:
>>> if 1:
... del __builtins__
... print(123)
...
123
>>> print(123)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'print' is not defined
自然地,您现在可能想知道如何从脚本中删除内置函数。答案应该很明显:你不能通过重新绑定一个名字来做到这一点,但你可以通过突变来做到这一点:
# foo2.py
__builtins__.__dict__.clear()
print(int) # <- NameError: name 'print' is not defined
作为最后的说明,__builtins__
名称完全绑定的事实是 CPython 的 implementation detail,并且明确记录在案:
Users should not touch __builtins__
; it is strictly an implementation detail.
不要依赖 __builtins__
做任何严肃的事情,如果您需要访问该范围,正确的方法是 import builtins
然后从那里开始。
CPython 不会在每次需要进行内置变量查找时查找 __builtins__
。每个框架对象都有一个 f_builtins
成员保存其内置变量 dict,内置变量查找从那里进行。
f_builtins
在创建框架对象时设置。如果一个新框架没有父框架(f_back
),或者一个与其父框架不同的全局变量dict,那么框架对象初始化查找 __builtins__
以设置 f_builtins
。 (如果新框架与其父框架共享一个全局字典,那么它将继承其父框架的 f_builtins
。)这是 __builtins__
参与内置变量查找的唯一方式。您可以在 _PyFrame_New_NoTrack
.
中查看处理此问题的代码
当您在脚本中删除 __builtins__
时,不会影响 f_builtins
。在脚本的堆栈框架中执行的其余代码仍然可以看到内置函数。一旦脚本完成并且 -i
使您进入交互模式,每个交互命令都会获得一个新的堆栈帧(没有父级),然后重复 __builtins__
查找。这是删除的 __builtins__
终于重要的时候。
我有一个包含以下内容的 python 脚本:
# foo.py
__builtins__ = 3
del __builtins__
print(int) # <- this still works
奇怪的是,使用 -i
标志执行此脚本会阻止 仅 REPL 访问内置函数:
aran-fey@starlight ~> python3 -i foo.py
<class 'int'>
>>> print(int)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'print' is not defined
为什么脚本可以访问内置函数,而 REPL 不能?
执行上下文不同。在 REPL 中,我们逐行工作 (Read, Eval, Print , Loop), 这允许在每个步骤之间更改全局执行范围的机会。但是执行模块的运行时是加载模块代码,然后在一个范围内执行。
在 CPython 中,通过在全局命名空间中查找名称 __builtins__
来找到与代码块执行相关联的内置命名空间;这应该绑定到字典或模块(在后一种情况下使用模块的字典)。当在__main__
模块中时,__builtins__
是内置模块builtins
,否则__builtins__
绑定到builtins
模块本身的字典。在您问题的两种情况下,我们都在 __main__
模块中。
重要的是 CPython 仅在开始执行您的代码之前 一次 查找内置函数。在 REPL 中,每次执行新语句时都会发生这种情况。但是当执行 python 脚本时,脚本的全部内容是一个单元。这就是为什么删除脚本中间的内置函数没有效果。
为了在 REPL 中更紧密地复制该上下文,您不会逐行输入模块的代码,而是使用复合语句:
>>> if 1:
... del __builtins__
... print(123)
...
123
>>> print(123)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'print' is not defined
自然地,您现在可能想知道如何从脚本中删除内置函数。答案应该很明显:你不能通过重新绑定一个名字来做到这一点,但你可以通过突变来做到这一点:
# foo2.py
__builtins__.__dict__.clear()
print(int) # <- NameError: name 'print' is not defined
作为最后的说明,__builtins__
名称完全绑定的事实是 CPython 的 implementation detail,并且明确记录在案:
Users should not touch
__builtins__
; it is strictly an implementation detail.
不要依赖 __builtins__
做任何严肃的事情,如果您需要访问该范围,正确的方法是 import builtins
然后从那里开始。
CPython 不会在每次需要进行内置变量查找时查找 __builtins__
。每个框架对象都有一个 f_builtins
成员保存其内置变量 dict,内置变量查找从那里进行。
f_builtins
在创建框架对象时设置。如果一个新框架没有父框架(f_back
),或者一个与其父框架不同的全局变量dict,那么框架对象初始化查找 __builtins__
以设置 f_builtins
。 (如果新框架与其父框架共享一个全局字典,那么它将继承其父框架的 f_builtins
。)这是 __builtins__
参与内置变量查找的唯一方式。您可以在 _PyFrame_New_NoTrack
.
当您在脚本中删除 __builtins__
时,不会影响 f_builtins
。在脚本的堆栈框架中执行的其余代码仍然可以看到内置函数。一旦脚本完成并且 -i
使您进入交互模式,每个交互命令都会获得一个新的堆栈帧(没有父级),然后重复 __builtins__
查找。这是删除的 __builtins__
终于重要的时候。