Aks*_*rit 16 python internals python-internals
在昨天的一个问题中,在评论中,我开始知道在python __code__函数中的atrribute是可变的.因此我可以编写如下代码
def foo():
print "Hello"
def foo2():
print "Hello 2"
foo()
foo.__code__ = foo2.__code__
foo()
Run Code Online (Sandbox Code Playgroud)
产量
Hello
Hello 2
Run Code Online (Sandbox Code Playgroud)
我试过谷歌搜索,但要么因为没有信息(我非常怀疑这一点),或者关键字(__code__)不容易搜索,我找不到一个用例.
它似乎不是"因为Python中的大多数东西都是可变的"也是一个合理的答案,因为函数的其他属性 - __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 */
};
Run Code Online (Sandbox Code Playgroud)
为什么__code__在其他属性为只读时可写?
事实上,Python中的大多数东西都是可变的。所以真正的问题是,为什么是__closure__和__globals__ 不是?
答案最初看起来很简单。这两个东西都是函数可能需要的变量的容器。代码对象本身并不携带它的封闭变量和全局变量;它只知道如何从函数中获取它们。当函数被调用时,它从这两个属性中获取实际值。
但是范围本身是可变的,所以这个答案并不令人满意。我们需要解释为什么特别修改这些东西会破坏东西。
对于__closure__,我们可以查看它的结构。它不是一个映射,而是一个单元元组。它不知道封闭变量的名称。当代码对象查找一个封闭变量时,它需要知道它在元组中的位置;它们一对一匹配,co_freevars也是只读的。如果元组的大小错误或根本不是元组,则如果底层 C 代码没有预料到这种情况,则此机制可能会严重崩溃(阅读:段错误)。强制 C 代码检查元组的类型和大小是不必要的繁重工作,可以通过将属性设置为只读来消除它。如果你尝试__code__用不同数量的自由变量替换,你会得到一个错误,所以大小总是正确的。
对于__globals__,解释不太明显,但我会推测。范围查找机制期望在任何时候都可以访问全局命名空间。实际上,如果编译器可以证明没有其他命名空间具有具有特定名称的变量,那么字节码可能会被硬编码为直接进入全局命名空间。如果全局命名空间突然出现None或其他一些非映射对象,则 C 代码可能会再次出现严重的错误行为。同样,让代码执行不必要的类型检查会浪费 CPU 周期。
另一种可能性是(通常声明的)函数借用了对模块全局命名空间的引用,并且使属性可写会导致引用计数混乱。我可以想象这种设计,但我不确定这是一个好主意,因为可以使用生命周期可能比拥有模块的生命周期短的对象显式构造函数,并且这些需要特殊情况。