【问题标题】:Why does deleting a global variable named __builtins__ prevent only the REPL from accessing builtins?为什么删除名为 __builtins__ 的全局变量只会阻止 REPL 访问内置函数?
【发布时间】:2018-09-07 11:51:22
【问题描述】:

我有一个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 不能?

【问题讨论】:

    标签: python read-eval-print-loop built-in cpython


    【解决方案1】:

    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__ 终于重要的时候了。

    【讨论】:

      【解决方案2】:

      执行上下文不同。在 REPL 中,我们逐行工作(Read、Eval、Print、L oop),它允许全局执行范围在每个步骤之间发生变化。但是执行模块的运行时是加载模块代码,然后在一个作用域内执行。

      在 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,并且已明确记录:

      用户不应触碰__builtins__;这严格来说是一个实现细节。

      不要依赖__builtins__ 做任何严重的事情,如果您需要访问该范围,正确的方法是访问import builtins 并从那里开始。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-20
        • 2012-02-26
        • 1970-01-01
        • 1970-01-01
        • 2021-12-10
        • 1970-01-01
        相关资源
        最近更新 更多