关于python:schr?丁格变量:如果你正在检查它的存在,那么__class__ 单元会神奇地出现?

Schr?dinger's variable: the __class__ cell magically appears if you're checking for its presence?

这里有一个惊喜:

1
2
3
4
5
6
7
8
9
10
>>> 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

似乎仅仅提到__class__就被解析器显式地检查了?否则我们应该得到

1
NameError: name '__class__' is not defined

实际上,如果您修改为只检查密钥,即检查'__class__' in locals(),那么我们只能在预期的范围内使用self

如何将这个变量神奇地注入作用域?我猜这与super有关,但我没有使用super,所以如果不需要,为什么编译器会在这里创建一个隐式的闭包引用?


这是Python3实现无参数super时的一个奇怪的交互。方法中对super的访问会触发添加一个隐藏的__class__闭包变量,该变量引用定义该方法的类。解析器在一个方法中特殊情况下加载名称super,方法的符号表中也添加了__class__,然后其他相关代码都查找__class__,而不是super。但是,如果您尝试自己访问__class__,所有寻找__class__的代码都会看到它,并认为它应该执行super处理!

这里是它在符号表中添加名称__class__,如果它看到super

1
2
3
4
5
6
7
8
9
10
11
12
13
case Name_kind:
    if (!symtable_add_def(st, e->v.Name.id,
                          e->v.Name.ctx == Load ? USE : DEF_LOCAL))
        VISIT_QUIT(st, 0);
    /* Special-case super: it counts as a use of __class__ */
    if (e->v.Name.ctx == Load &&
        st->st_cur->ste_type == FunctionBlock &&
        !PyUnicode_CompareWithASCIIString(e->v.Name.id,"super")) {
        if (!GET_IDENTIFIER(__class__) ||
            !symtable_add_def(st, __class__, USE))
            VISIT_QUIT(st, 0);
    }
    break;

这是drop_class_free,设置ste_needs_class_closure

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}

检查ste_needs_class_closure并创建隐式单元格的编译器部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
if (u->u_ste->ste_needs_class_closure) {
    /* Cook up an implicit __class__ cell. */
    _Py_IDENTIFIER(__class__);
    PyObject *tuple, *name, *zero;
    int res;
    assert(u->u_scope_type == COMPILER_SCOPE_CLASS);
    assert(PyDict_Size(u->u_cellvars) == 0);
    name = _PyUnicode_FromId(&PyId___class__);
    if (!name) {
        compiler_unit_free(u);
        return 0;
    }
    ...

有更多相关的代码,但是太多了,无法包含所有代码。如果你想看更多的话,你可以去看Python/compile.cPython/symtable.c

如果尝试使用名为__class__的变量,则可能会出现一些奇怪的错误:

1
2
3
4
5
6
class Foo:
    def f(self):
        __class__ = 3
        super()

Foo().f()

输出:

1
2
3
4
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__的赋值意味着__class__是一个局部变量,而不是一个闭包变量,因此闭包单元super()的需求不存在。

1
2
3
4
5
6
7
8
9
def f():
    __class__ = 2
    class Foo:
        def f(self):
            print(__class__)

    Foo().f()

f()

输出:

1
<class '__main__.f.<locals>.Foo'>

即使封闭范围中有一个实际的__class__变量,__class__的特殊大小写意味着您得到的是类而不是封闭范围的变量值。


https://docs.python.org/3/reference/datamodel.html创建类对象

__class__ is an implicit closure reference created by the compiler if any methods in a class body refer to either __class__ or super. This allows the zero argument form of super() to correctly identify the class being defined based on lexical scoping, while the class or instance that was used to make the current call is identified based on the first argument passed to the method.