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

时间:2015-07-23 22:50:53

标签: python internals python-internals

在昨天的一个问题中,在评论中,我发现在python __code__函数中,atrribute是一个可变的。因此我可以编写如下代码

def foo():
    print "Hello"

def foo2():
    print "Hello 2"

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

输出

Hello
Hello 2

我尝试使用Google搜索,但要么是因为没有信息(我非常怀疑这一点),要么关键字(__code__)不容易搜索,我找不到一个用例。

它似乎不是“因为Python中的大多数东西都是可变的”也是一个合理的答案,因为函数的其他属性 - __closure____globals__ - 是显式只读的(来自{ {3}}):

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__可写,而其他属性是只读的?

1 个答案:

答案 0 :(得分:4)

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

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

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

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

对于__globals__,解释不太明显,但我推测。范围查找机制希望始终可以访问全局命名空间。实际上,如果编译器可以证明其他命名空间没有具有特定名称的变量,则字节码可以hard-coded直接进入全局命名空间。如果全局命名空间突然None或其他一些非映射对象,那么C代码可能再一次暴力行为不端。再次,使代码执行不必要的类型检查将浪费CPU周期。

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