Python Schr&xF6;丁格尔';s变量:如果';你在检查它的存在吗?
这里有一个惊喜:Python Schr&xF6;丁格尔';s变量:如果';你在检查它的存在吗?,python,python-3.x,closures,python-datamodel,Python,Python 3.x,Closures,Python Datamodel,这里有一个惊喜: >>> class B: ... print(locals()) ... def foo(self): ... print(locals()) ... print(__class__ in locals().values()) ... {'__module__': '__main__', '__qualname__': 'B'} >>> B().foo() {'__class_
>>> class B:
... print(locals())
... def foo(self):
... print(locals())
... print(__class__ in locals().values())
...
{'__module__': '__main__', '__qualname__': 'B'}
>>> B().foo()
{'__class__': <class '__main__.B'>, 'self': <__main__.B object at 0x7fffe916b4a8>}
True
事实上,如果您修改为只检查键,即检查locals()中的“\uuuuu class\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
这个变量是如何神奇地被注入作用域的?我猜这与super
有关-但是我没有使用super
,那么如果不需要,编译器为什么要在这里创建一个隐式闭包引用呢?这是Python 3实现无参数super
时的一个奇怪的交互。对方法中的super
的访问会触发添加一个隐藏的\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu闭包变量,该变量引用定义该方法的类。解析器通过向方法的符号表中添加\uuuuuuu class\uuuuuuu
来对方法中的名称super
进行特殊处理,然后相关代码的其余部分都会查找\uuuuuuu class\uuuuuuuuu
而不是super
。但是,如果您尝试自己访问\uuuuu class\uuuuu
,所有查找\uuuuuu class\uuuuu
的代码都会看到它,并认为它应该执行超级处理
:
下面是设置ste\u needs\u class\u closure
:
static int
drop_class_free(PySTEntryObject *ste, PyObject *free)
{
int res;
if (!GET_IDENTIFIER(__class__))
return 0;
res = PySet_Discard(free, __class__);
if (res < 0)
return 0;
if (res)
ste->ste_needs_class_closure = 1;
return 1;
}
有更多相关的代码,但太多了,无法包含所有代码。如果你想看更多,你可以去哪里看
如果尝试使用名为\uuuuu class\uuuuu
的变量,可能会出现一些奇怪的错误:
class Foo:
def f(self):
__class__ = 3
super()
Foo().f()
输出:
Traceback (most recent call last):
File "./prog.py", line 6, in <module>
File "./prog.py", line 4, in f
RuntimeError: super(): __class__ cell not found
<class '__main__.f.<locals>.Foo'>
输出:
Traceback (most recent call last):
File "./prog.py", line 6, in <module>
File "./prog.py", line 4, in f
RuntimeError: super(): __class__ cell not found
<class '__main__.f.<locals>.Foo'>
即使在封闭范围内有一个实际的\uuuuuu class\uuuuu
变量,但\uuuuuu class\uuuu
的特殊大小写意味着您得到的是类而不是封闭范围内的变量值。
如果类体中的任何方法引用了\uuuuuuuuu class\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。这允许super()
的零参数形式正确标识基于词法作用域定义的类,而用于进行当前调用的类或实例则基于传递给方法的第一个参数进行标识
这有记录吗?好吧,现在我觉得挖掘这个来源很愚蠢。这是一个比我更好的答案。这两个答案都没有真正向我解释为什么在没有使用super时需要创建引用。@wim:不需要创建引用。反正就是这样。我猜实现恰好是这样工作的,然后他们记录了它,因为它不是一个值得更改的大问题,而不是设计上的问题。@user2357112我不同意-你的答案更好。事实上,它的记录并没有告诉我们任何关于魔术是如何工作的,它只是提供了一个令人信服的解释,为什么它会在那里。谢谢你回答如何。现在另一个问题是为什么:为什么这个\uuuuuu class\uuuuuuu
引用并不总是可用的,而是只有在使用\uuuuuuuuu class\uuuuuuuuu
或super()
时才可用?较高的堆栈帧可能需要访问它才能在较低的堆栈帧中获取调用类。例如,@Maggyero:额外的闭包变量会带来较小但一致的每次调用性能损失。让所有Python代码中的每一个用户定义方法仅仅为了启用更奇怪的反模式内省而付出代价是不值得的。另外,要真正按照您所期望的方式工作,它必须由嵌套作用域继承,这也会降低理解和生成器表达式的速度。好的,谢谢您的输入。所以你会因为表现的原因而拒绝(PR实际上被拒绝了,但我不知道原因)?@Maggyero:PEP 3130没有提出你想要的东西。在PEP 3130语义下,不使用\uuuuuuu class\uuuuuuu
的方法不需要携带\uuuuuuu class\uuuuuuuu
引用。闭包变量不是只能在闭包中看到这个问题吗?
def f():
__class__ = 2
class Foo:
def f(self):
print(__class__)
Foo().f()
f()
<class '__main__.f.<locals>.Foo'>