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

为什么函数(Python)的代码是可变的,python,internals,python-internals,Python,Internals,Python Internals,在昨天的一个问题中,我在评论中了解到,在python中,函数的属性是可变的。因此,我可以编写如下代码 def foo(): print "Hello" def foo2(): print "Hello 2" foo() foo.__code__ = foo2.__code__ foo() 输出 Hello Hello 2 我尝试过谷歌搜索,但要么因为没有信息(我对此表示高度怀疑),要么因为关键字(\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

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

def foo():
    print "Hello"

def foo2():
    print "Hello 2"

foo()
foo.__code__ = foo2.__code__
foo()
输出

Hello
Hello 2
我尝试过谷歌搜索,但要么因为没有信息(我对此表示高度怀疑),要么因为关键字(
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

“因为Python中的大多数东西都是可变的”似乎也不是一个合理的答案,因为函数的其他属性--
\uuuuu closure\uuuuu
\uuuuu globals\uuuuu
都是显式只读的(从):


为什么其他属性是只读的而
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu>是可写的?

事实是,Python中的大多数东西都是。所以真正的问题是,为什么
\uuuu closure\uuuu
\uuu globals\uuuu
不是

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

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

对于
\uuuu闭包\uuuu
,我们可以查看它的结构。它不是一个映射,而是一个单元元组。它不知道封闭变量的名称。当代码对象查找闭合变量时,它需要知道它在元组中的位置;它们与
co_freevars
一一匹配,后者也是只读的。如果元组的大小不正确或者根本不是元组,那么这种机制就会崩溃,如果底层C代码没有预料到这种情况,那么这种机制可能会严重崩溃(读:segfaults)。强制C代码检查元组的类型和大小是不必要的繁忙工作,这可以通过使属性为只读来消除。如果您试图用具有不同数量自由变量的内容替换
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
,那么大小总是正确的

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


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

在Python中,你无法阻止任何这种“邪恶”,不管是不是。要么信任所使用的模块,要么不使用它们。是。我发现通常情况都是这样。但还是想问一下。练习安全堆栈而不是挂接奇怪的API。我会给你一个特殊的设计选择。除了
\uuu全局变量和
\uu闭包
之外,函数中的所有内容都是可写的。为什么只有这两个是只读的是任何人的猜测。@AkshatHarit哎呀,看起来我的回答(“Python中的大多数内容”)没有帮助。我希望您能原谅我对您的问题进行了一些编辑,以澄清为什么这实际上是一个有趣而非直截了当的问题。我不相信
\uuuu closure\uuuuu
的解释:添加一个进行某种类型检查的setter会很容易(例如,请参阅
\uuu code\uuu
的setter,它正是这样做的:)@戴维德沃利弗:它仍然是最不友好的界面之一。你必须把变量的顺序弄对,它不是一个普通对象的元组,而是一个“单元”的元组。在核心开发人员的心目中,类型检查代码可能比它的价值更麻烦。考虑到
g={k:v代表k,v在函数中,\uu globals\uuuuuuuu.items()如果k in允许};新建函数=类型。函数类型(func.\uuuuu代码\uuuuuuu,g,func.\uuuuu名称\uuuuuuuuuu,func.\uuuuuuu默认值\uuuuuuuuuuuuu,func.\uuuuuuuuu闭包\uuuuuuuuuuuuuuuu)有效python@NightShadeQueen:您是否尝试过使用
?我想这会让你犯错误。是的,打字错误。不过,它确实让我用defaultdict为globals创建了一个函数
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 */
};