class属性查找规则?

时间:2016-08-26 15:18:39

标签: python python-internals

>>> class D:
...     __class__ = 1
...     __name__ = 2
...
>>> D.__class__
<class 'type'>
>>> D().__class__
1
>>> D.__name__
'D'
>>> D().__name__
2

为什么D.__class__会返回类的名称,而D().__class__会返回D类中已定义的属性?

__class____name__等内置属性来自何处?

我怀疑__name____class__是生活在object班级或某处的简单描述符,但是无法看到。

在我的理解中,Python中的属性查找规则如下,省略了描述符的条件等。:

Instance --> Class --> Class.__bases__ and the bases of the other classes as well

鉴于某个类是元类的实例,在这种情况下为type,为什么D.__class____class__中找不到D.__dict__

1 个答案:

答案 0 :(得分:9)

名称__class____name__是特殊的。两者都是数据描述符__name__对象上定义了type__class__上定义了object(所有新式类的基类):

>>> type.__dict__['__name__']
<attribute '__name__' of 'type' objects>
>>> type.__dict__['__name__'].__get__
<method-wrapper '__get__' of getset_descriptor object at 0x1059ea870>
>>> type.__dict__['__name__'].__set__
<method-wrapper '__set__' of getset_descriptor object at 0x1059ea870>
>>> object.__dict__['__class__']
<attribute '__class__' of 'object' objects>
>>> object.__dict__['__class__'].__get__
<method-wrapper '__get__' of getset_descriptor object at 0x1059ea2d0>
>>> object.__dict__['__class__'].__set__
<method-wrapper '__set__' of getset_descriptor object at 0x1059ea2d0>

因为它们是数据描述符,type.__getattribute__ method(用于类的属性访问)将忽略类__dict__中设置的任何属性,并且只使用描述符本身:

>>> type.__getattribute__(Foo, '__class__')
<class 'type'>
>>> type.__getattribute__(Foo, '__name__')
'Foo'

有趣的事实:type派生自object Python中的所有是一个对象),这就是__class__type上找到的原因检查数据描述符:

>>> type.__mro__
(<class 'type'>, <class 'object'>)

type.__getattribute__(D, ...)直接用作未绑定的方法,而不是D.__getattribute__(),因为all special method access goes to the type)。

参见Descriptor Howto构成数据描述符的内容及其重要性:

  

如果对象同时定义__get__()__set__(),则将其视为数据描述符。仅定义__get__()的描述符称为非数据描述符(它们通常用于方法,但其他用途也是可能的)。

     

数据和非数据描述符的不同之处在于如何根据实例字典中的条目计算覆盖。如果实例的字典具有与数据描述符同名的条目,则数据描述符优先。如果实例的字典具有与非数据描述符同名的条目,则字典条目优先。

对于type上的数据描述符,类只是另一个实例。

因此,在查找__class____name__属性时,D.__dict__命名空间中定义的内容无关紧要,因为在命名空间中找到了数据描述符由type和它是MRO。

这些描述符在typeobject.c C code

中定义
static PyGetSetDef type_getsets[] = {
    {"__name__", (getter)type_name, (setter)type_set_name, NULL},
    /* ... several more ... */
}

/* ... */

PyTypeObject PyType_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "type",                                     /* tp_name */
    /* ... many type definition entries ... */
    type_getsets,                               /* tp_getset */
    /* ... many type definition entries ... */
}

/* ... */

static PyGetSetDef object_getsets[] = {
    {"__class__", object_get_class, object_set_class,
     PyDoc_STR("the object's class")},
    {0}
};

PyTypeObject PyBaseObject_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "object",                                   /* tp_name */
    /* ... many type definition entries ... */
    object_getsets,                             /* tp_getset */
    /* ... many type definition entries ... */
}

在实例上,使用object.__getattribute__,它会在__name__映射中找到__class__D.__dict__条目,然后才会在object上找到数据描述符{1}}或type

但是,如果省略其中任何一项,那么在D()上查找名称只会__class__作为D的MRO中的数据描述符(因此,在object上)。找不到__name__,因为在解析实例属性时不考虑元类型。

因此,您可以在实例上设置__name__,但不能在__class__

设置
>>> class E: pass
...
>>> e = E()
>>> e.__class__
<class '__main__.E'>
>>> e.__name__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'E' object has no attribute '__name__'
>>> e.__dict__['__class__'] = 'ignored'
>>> e.__class__
<class '__main__.E'>
>>> e.__name__ = 'this just works'
>>> e.__name__
'this just works'