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?
type.__setattr__
has a check to prevent setting attributes on types likeint
, 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__
:and if we examine
PyBaseObject_Type
, we see it usesPyObject_GenericSetAttr
for its__setattr__
, the same call that appears halfway throughtype_setattro
.Thus,
type.__setattr__
is likeobject.__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, likeint
ornumpy.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.