type.__setattr__ 与 object.__setattr__ 有何不同?

How different is type.__setattr__ from object.__setattr__?

type.__setattr__ 用于 classes,基本上是 metaclasses 的实例。另一方面,object.__setattr__ 用于 classes 的实例。这是完全明白的。

我看不出这两种方法有什么显着差异,至少在 Python 水平上,我注意到这两种方法使用相同的属性分配过程,如果我错了请纠正我:

假设a是一个用户自定义class的实例,只是一个普通的class:

class A:
    pass

a = A()
a.x = ...

然后 a.x = .. 调用 type(a).__setattr__(...) 执行以下步骤:

注意:type(a).__setattr__ 会在 object 内置 class

中找到 __setattr__

1) 在type(a).__mro__中查找数据描述符。

2) 如果找到数据描述符,调用它的__set__方法并退出。

3) 如果在type(a).__mro__中没有找到数据描述符,则将属性添加到a.__dict__a.__dict__['x'] = ...


With classes--metaclasses 的实例,过程类似:

class A(metaclass=type):
    pass

然后:A.x = ... 被转换为 type(A).__setattr__(...),执行以下步骤:

注意:type(A).__setattr__ 会在 type 内置 class

中找到 __setattr__

1) 在type(A).__mro__

中查找数据描述符

2) 如果找到数据描述符,调用它的__set__方法并退出。

3) 如果在type(A).__mro__中没有找到数据描述符,则将属性添加到A.__dict__a.__dict__['x'] = ...

但是 object.__setattr__ 不适用于 classes:

>>> object.__setattr__(A, 'x', ...)
TypeError: can't apply this __setattr__ to type object

反之亦然,type.__setattr__ 不适用于 A 的实例:

>>> type.__setattr__(A(), 'x', ...)
TypeError: descriptor '__setattr__' requires a 'type' object but received a 'A'

嗯!这两种方法之间一定有什么不同。这是微妙的,但仍然是真实的!

推测这两个方法在__setattr__里面执行相同的步骤,type.__setattr__object.__setattr__有什么区别,所以type.__setattr__被限制为classes 和 object.__setattr__ 仅限于 classes?

的实例

type.__setattr__ 有一个检查以防止在 int 等类型上设置属性,并且它做了一堆普通对象不需要的不可见清理。


让我们深入了解一下!这里是 type.__setattr__:

static int
type_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
{
    if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
        PyErr_Format(
            PyExc_TypeError,
            "can't set attributes of built-in/extension type '%s'",
            type->tp_name);
        return -1;
    }
    if (PyObject_GenericSetAttr((PyObject *)type, name, value) < 0)
        return -1;
    return update_slot(type, name);
}

如果我们检查 PyBaseObject_Type,我们会看到它使用 PyObject_GenericSetAttr 作为其 __setattr__,与 type_setattro.

中途出现的相同调用

因此,type.__setattr__ 类似于 object.__setattr__,但围绕它进行了一些额外的处理。

首先,if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) 检查禁止对用 C 编写的类型进行属性赋值,例如 intnumpy.array,因为对这些类型赋值会严重搞砸 Python 不熟悉 C API 的人可能不会想到的内部方式。

其次,在 PyObject_GenericSetAttr 调用更新类型的字典或从元 class 调用适当的描述符后,update_slot 修复任何 slots 受属性分配的影响。这些槽是 C 级函数指针,实现实例分配、in 检查、+、释放等功能。它们中的大多数都有相应的 Python 级方法,例如 __contains____add__,如果其中一个 Python 级别的方法被重新分配,相应的插槽(或多个插槽)也必须更新。 update_slot 还更新 class 的所有后代上的槽,并使用于类型对象属性的内部属性缓存中的条目无效。