为什么 __code__ 对于函数 (Python) 是可变的

Why is __code__ for a function(Python) mutable

在昨天的前一个问题中,在评论中,我了解到在 python __code__ 中函数的属性是可变的。因此我可以编写如下代码

def foo():
    print "Hello"

def foo2():
    print "Hello 2"

foo()
foo.__code__ = foo2.__code__
foo()

输出

Hello
Hello 2

我试过谷歌搜索,但要么是因为没有信息(我对此非常怀疑),要么是关键字 (__code__) 不容易搜索到,我找不到这个的用例。

似乎 "because most things in Python are mutable" 也不是一个合理的答案,因为函数的其他属性 — __closure____globals__ — 是明确只读的(来自 Objects/funcobject.c):

static PyMemberDef func_memberlist[] = {
    {"__closure__",   T_OBJECT,     OFF(func_closure),
     RESTRICTED|READONLY},
    {"__doc__",       T_OBJECT,     OFF(func_doc), PY_WRITE_RESTRICTED},
    {"__globals__",   T_OBJECT,     OFF(func_globals),
     RESTRICTED|READONLY},
    {"__module__",    T_OBJECT,     OFF(func_module), PY_WRITE_RESTRICTED},
    {NULL}  /* Sentinel */
};

为什么 __code__ 是可写的而其他属性是只读的?

事实是,Python中的大部分内容都是可变的。所以真正的问题是,为什么 __closure____globals__ 不是

答案最初看起来很简单。这两件事都是函数可能需要的变量的容器。代码对象本身并不携带它的封闭变量和全局变量;它只知道如何从函数中获取它们。它在调用函数时从这两个属性中获取实际值。

但是作用域本身是可变的,所以这个答案并不令人满意。我们需要解释为什么特别修改这些东西会破坏东西。

对于__closure__,我们可以看看它的结构。它不是映射,而是单元格的元组。它不知道封闭变量的名称。当代码对象查找一个封闭变量时,它需要知道它在元组中的位置;它们与 co_freevars 一对一匹配,这也是只读的。如果元组的大小错误或者根本不是元组,那么如果底层 C 代码没有预料到这种情况,这种机制可能会崩溃(读作:段错误)。强制 C 代码检查元组的类型和大小是不必要的繁重工作,可以通过将属性设为只读来消除这一点。如果您尝试将 __code__ 替换为采用不同数量的自由变量的东西,you get an error,那么大小总是正确的。

对于 __globals__,解释不是很明显,但我会推测。范围查找机制期望在任何时候都可以访问全局命名空间。实际上,如果编译器可以证明没有其他命名空间将具有具有特定名称的变量,则字节码可能 hard-coded 直接进入全局命名空间。如果全局命名空间突然变成 None 或其他一些非映射对象,C 代码可能会再次出现严重的错误行为。同样,让代码执行不必要的类型检查会浪费 CPU 个周期。

另一种可能性是(通常声明的)函数 borrow a reference 到模块的全局命名空间,并且使属性可写会导致引用计数混乱。我可以想象这种设计,但我不太确定这是个好主意,因为可以使用生命周期可能比拥有模块的生命周期短的对象显式构造函数,并且这些需要特殊情况。