In Python, what is the rationale for which object.__setattr__
and type.__setattr__
raise an AttributeError
during attribute update if the type has an attribute which is a data descriptor without a __set__
method? Likewise, what is the rationale for which object.__delattr__
and type.__delattr__
raise an AttributeError
during attribute deletion if the type has an attribute which is a data descriptor without a __delete__
method?
I am asking this because I have noticed that object.__getattribute__
and type.__getattribute__
do not raise an AttributeError
during attribute lookup if the type has an attribute which is a data descriptor without a __get__
method.
Here is a simple program illustrating the differences between attribute lookup by object.__getattribute__
on the one hand (AttributeError
is not raised), and attribute update by object.__setattr__
and attribute deletion by object.__delattr__
on the other hand (AttributeError
is raised):
class DataDescriptor1: # missing __get__
def __set__(self, instance, value): pass
def __delete__(self, instance): pass
class DataDescriptor2: # missing __set__
def __get__(self, instance, owner=None): pass
def __delete__(self, instance): pass
class DataDescriptor3: # missing __delete__
def __get__(self, instance, owner=None): pass
def __set__(self, instance, value): pass
class A:
x = DataDescriptor1()
y = DataDescriptor2()
z = DataDescriptor3()
a = A()
vars(a).update({'x': 'foo', 'y': 'bar', 'z': 'baz'})
a.x
# actual: returns 'foo'
# expected: returns 'foo'
a.y = 'qux'
# actual: raises AttributeError: __set__
# expected: vars(a)['y'] == 'qux'
del a.z
# actual: raises AttributeError: __delete__
# expected: 'z' not in vars(a)
Here is another simple program illustrating the differences between attribute lookup by type.__getattribute__
on the one hand (AttributeError
is not raised), and attribute update by type.__setattr__
and attribute deletion by type.__delattr__
on the other hand (AttributeError
is raised):
class DataDescriptor1: # missing __get__
def __set__(self, instance, value): pass
def __delete__(self, instance): pass
class DataDescriptor2: # missing __set__
def __get__(self, instance, owner=None): pass
def __delete__(self, instance): pass
class DataDescriptor3: # missing __delete__
def __get__(self, instance, owner=None): pass
def __set__(self, instance, value): pass
class M(type):
x = DataDescriptor1()
y = DataDescriptor2()
z = DataDescriptor3()
class A(metaclass=M):
x = 'foo'
y = 'bar'
z = 'baz'
A.x
# actual: returns 'foo'
# expected: returns 'foo'
A.y = 'qux'
# actual: raises AttributeError: __set__
# expected: vars(A)['y'] == 'qux'
del A.z
# actual: raises AttributeError: __delete__
# expected: 'z' not in vars(A)
I would expect the instance dictionary to be mutated instead of getting an AttributeError
for attribute update and attribute deletion. Attribute lookup returns a value from the instance dictionary, so I am wondering why attribute update and attribute deletion do not use the instance dictionary as well (like they would do if the type did not have an attribute which is a data descriptor).
slot_tp_descr_set
returns-1
both when the looked up__set__
or__delete__
method is absent and when the looked up__set__
or__delete__
method is present but its call raises an exception (frequentlyAttributeError
). So the calling function_PyObject_GenericSetAttrWithDict
has no way to make the distinction and fall back on the instance in the former case. – Vevina