问题
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:
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.
来源:https://stackoverflow.com/questions/43739608/how-different-is-type-setattr-from-object-setattr