How different is type.__setattr__ from object.__se

2019-03-26 01:06发布

type.__setattr__ is used for classes, basically instances of metaclasses. object.__setattr__ on the other hand, is used for instances of classes. This is totally understood.

I don't see a significant difference between the two method, at least at Python level, I notice the two use the same procedures for attribute assignment, correct me if I'm wrong:

Suppose a is an instance of a user-defined class, just a normal class:

class A:
    pass

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

then a.x = .. invokes type(a).__setattr__(...) which performs the following steps:

Note: type(a).__setattr__ will find __setattr__ in object builtin class

1) Look for a data descriptor in type(a).__mro__.

2) If a data descriptor was found, call its __set__ method and exit.

3) If no data descriptor was found in type(a).__mro__, then add attribute to a.__dict__, a.__dict__['x'] = ...


With classes--instances of metaclasses, the process is similar:

class A(metaclass=type):
    pass

then: A.x = ... is translated to type(A).__setattr__(...) which performs the following steps:

Note: type(A).__setattr__ will find __setattr__ in type builtin class

1) Look for a data descriptor in type(A).__mro__

2) If a data descriptor was found, call its __set__ method and exit.

3) If no data descriptor was found in type(A).__mro__, then add attribute to A.__dict__, a.__dict__['x'] = ...

But object.__setattr__ doesn't work for classes:

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

and vice versa, type.__setattr__ doesn't work for instances of A:

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

Hmmm! There must be something different between the two methods. This is subtle, but true nonetheless!

Presumably the two methods perform the same steps inside __setattr__, what is the difference between type.__setattr__ and object.__setattr__ so that type.__setattr__ is limited to classes and object.__setattr__ is limited to instances of classes?

1条回答
Fickle 薄情
2楼-- · 2019-03-26 01:25

type.__setattr__ has a check to prevent setting attributes on types like int, and it does a bunch of invisible cleanup that isn't needed for normal objects.


Let's take a look under the hood! Here's 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);
}

and if we examine PyBaseObject_Type, we see it uses PyObject_GenericSetAttr for its __setattr__, the same call that appears halfway through type_setattro.

Thus, type.__setattr__ is like object.__setattr__, but with some additional handling wrapped around it.

First, the if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) check prohibits attribute assignment on types written in C, like int or numpy.array, because assigning attributes on those can seriously screw up the Python internals in ways someone unfamiliar with the C API might not expect.

Second, after the PyObject_GenericSetAttr call updates the type's dict or calls an appropriate descriptor from the metaclass, update_slot fixes up any slots affected by the attribute assignment. These slots are C-level function pointers that implement functionality like instance allocation, in checks, +, deallocation, etc. Most of them have corresponding Python-level methods, like __contains__ or __add__, and if one of those Python-level methods is reassigned, the corresponding slot (or slots) have to be updated, too. update_slot also updates slots on all descendants of the class, and it invalidates entries in an internal attribute cache used for type object attributes.

查看更多
登录 后发表回答