New submission from Géry <gery.o...@gmail.com>: Currently, the `object.__setattr__` and `type.__setattr__` methods raise an `AttributeError` during attribute *update* on an instance if its type has an attribute which is a *data* descriptor without a `__set__` method. Likewise, the `object.__delattr__` and `type.__delattr__` methods raise an `AttributeError` during attribute *deletion* on an instance if its type has an attribute which is a *data* descriptor without a `__delete__` method.
This should not be the case. When update/deletion is impossible through a data descriptor found on the type, update/deletion should carry its process on the instance, like when there is no data descriptor found on the type. And this is what the `object.__getattribute__` and `type.__getattribute__` methods already do: they do *not* raise an `AttributeError` during attribute *lookup* on an instance if its type has an attribute which is a *data* descriptor without a `__get__` method. See [the discussion on Python Discuss](https://discuss.python.org/t/why-do-setattr-and-delattr-raise-an-attributeerror-in-this-case/7836?u=maggyero). 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): ```python 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): ```python 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) ``` ---------- components: Interpreter Core messages: 389598 nosy: maggyero priority: normal severity: normal status: open title: Do not raise AttributeError on instance attribute update/deletion if data descriptor with missing __set__/__delete__ method found on its type type: behavior versions: Python 3.10, Python 3.6, Python 3.7, Python 3.8, Python 3.9 _______________________________________ Python tracker <rep...@bugs.python.org> <https://bugs.python.org/issue43639> _______________________________________ _______________________________________________ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com