Please consider mentioning property without setter when an attribute can't be set

Hello, consider: class C: @property def f(self) -> int: return 2 class D(C): pass D().f = 2 Gives: Traceback (most recent call last): File "/home/neil/src/cmm/a.py", line 10, in <module> D().f = 2 AttributeError: can't set attribute 'f' This can be a pain to debug when the property is buried in a base class. Would it make sense to mention the reason why the attribute can't be set, namely that it's on a property without a setter? Best, Neil

On Thu, Feb 10, 2022 at 02:27:42PM -0800, Neil Girdhar wrote:
AttributeError: can't set attribute 'f'
This can be a pain to debug when the property is buried in a base class.
Would it make sense to mention the reason why the attribute can't be set, namely that it's on a property without a setter?
I have no objection to changing the error message, I'm sure it's a small enough change that you should just open a ticket on b.p.o. for it. But I don't expect that it will be particularly useful either. If you can't set an attribute on an object, aren't there three obvious causes to check? - the object has no __dict__, and so has no attributes at all; e.g. trying to set an attribute on a float; - the object has slots, but 'f' is not one of them; - or 'f' is a property with no setter (or a setter that raises AttributeError). Have I missed any common cases? The error messages are different in each case, but even if they were the same, there are three obvious causes to check, and property is one of them. I suppose that there are exotic failures that could happen in __getattribute__ or weird descriptors, or metaclass magic, etc, and *those* might be hard to debug, but it doesn't seem likely to me that a read-only property will be mysterious. Especially if you have the object available in the interactive interpreter, so you can inspect it with the various tools available: * dir(obj) * help(obj) * type(obj).f # Will display <property object at 0x...> etc. Its hard to keep the existence of a property secret in Python. So once you know that f is a property, it might be hard to work out which of the fifty-seven superclasses and mixins it came from *wink* but that's neither here nor there :-) Maybe reporting "can't set property 'f'" is good enough. -- Steve

I have to say I agree with Neil here. I was trying to think about what other reasons an attribute might be unsettable, and did not quickly come up with that list. And after reading your list, I tried to imagine what I’d tell my beginning students ! But if the error message is indeed unique then yes: "can't set property 'f'" Would be far more clear message. -CHB -- Christopher Barker, PhD (Chris) Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython

On Fri, Feb 11, 2022 at 08:00:54AM -0800, Christopher Barker wrote:
I'm curious what list you came up with if it excluded the three cases I suggested (no __dict__, __slots__, property). What other cases are there? There's dynamic attributes with __getattr__ and __getattribute__, and who knows what can be done with metaclass magic, but I'm impressed and kinda intimidated if they are what came to mind *before* objects without a dict :-)
And after reading your list, I tried to imagine what I’d tell my beginning students !
Tell them in what context? Have you already covered properties? If so, then it should be fairly obvious what to tell them: "Class, what do you think will happen if you have a property with no setter and you try to set its value?" If your class is experienced enough to have learned about classes and properties, they will surely guess some sort of exception will happen.
As I said, I don't object to the change in wording if it is easy enough to implement. -- Steve

On Sat, Feb 12, 2022 at 5:32 PM Steven D'Aprano <steve@pearwood.info> wrote:
You're giving me far too much credit -- I hadn't thought about it that much but "no __dict__" was all I cam up with quickly -- and I had no idea what other possibilities there were. (Once I was reminded of__slots__)
That's the easy part -- we cover that when we cover properties -- this whole thing is not an issue when you are working with your own code that you just added a property to. This issue Neil brought up is that if teh cause is p the chain of superclasses somewhere, you don't get much help from the error message. Telling newbies that that means that it's either a property with no setter, or am object without a __dict__, or one with __slots__ defined is not really very helpful.
I think we're all on the same page here. -CHB -- Christopher Barker, PhD (Chris) Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython

On 2/13/22, Christopher Barker <pythonchb@gmail.com> wrote:
The __slots__ case is due to the lack of a __dict__ slot. It can be manually added in __slots__ (though adding __dict__ back is uncommon), along with the __weakref__ slot. The exception message when there's no __dict__ is generally good enough. For example: >>> (1).x = None Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'int' object has no attribute 'x' It's clear that the object has no __dict__ and no descriptor named "x". However, the message gets confusing with partially implemented magic attributes. For example, implement __getattr__(), but not __setattr__() or __delattr__(): class C: __slots__ = () def __getattr__(self, name): class_name = self.__class__.__name__ if name == 'x': return 42 raise AttributeError(f'{class_name!r} object has no ' f'attribute {name!r}') >>> c = C() >>> c.x 42 >>> c.x = None Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'C' object has no attribute 'x' Add __setattr__(): def __setattr__(self, name, value): class_name = self.__class__.__name__ if name == 'x': raise AttributeError(f'attribute {name!r} of {class_name!r} ' 'objects is not writable') raise AttributeError(f'{class_name!r} object has no ' f'attribute {name!r}') >>> c = C() >>> c.x = None Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 12, in __setattr__ AttributeError: attribute 'x' of 'C' objects is not writable >>> del c.x Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'C' object has no attribute 'x' Add __delattr__(): def __delattr__(self, name): class_name = self.__class__.__name__ if name == 'x': raise AttributeError(f'attribute {name!r} of {class_name!r} ' 'objects is not writable') raise AttributeError(f'{class_name!r} object has no ' f'attribute {name!r}') >>> c = C() >>> c.x 42 >>> c.x = None Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 12, in __setattr__ AttributeError: attribute 'x' of 'C' objects is not writable >>> del c.x Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 19, in __delattr__ AttributeError: attribute 'x' of 'C' objects is not writable

Yeah, I have to agree with Neil. I had exactly this issue a couple years ago, and it took me an hour or two to figure out that it was a property/descriptor-protocol causing the issue, at which point the fix became trivial. Just knowing to think "it's a property, stupid!" was the hard part and just stating that in the error message would have saved me the frustration. On Fri, Feb 11, 2022 at 2:46 AM Neil Girdhar <mistersheik@gmail.com> wrote:

On Fri, 11 Feb 2022 at 18:36, Eric Fahlgren <ericfahlgren@gmail.com> wrote:
Yeah, I have to agree with Neil. I had exactly this issue a couple years ago, and it took me an hour or two to figure out that it was a property/descriptor-protocol causing the issue, at which point the fix became trivial. Just knowing to think "it's a property, stupid!" was the hard part and just stating that in the error message would have saved me the frustration.
I'm inclined to say just raise an issue on bpo. If it's easy enough, it'll just get done. If it's hard, having lots of people support the idea won't make it any easier. I don't think this is something that particularly needs evidence of community support before asking for it. Paul

On 2/11/22, Paul Moore <p.f.moore@gmail.com> wrote:
The error message is in property_descr_set() in Objects/descrobject.c. I agree that it should state that the attribute is a property. Python developers know that a property requires a getter, setter, and deleter method in order to function like a regular, mutable attribute. If not, help(property) explains it all clearly.

On Thu, Feb 10, 2022 at 02:27:42PM -0800, Neil Girdhar wrote:
AttributeError: can't set attribute 'f'
This can be a pain to debug when the property is buried in a base class.
Would it make sense to mention the reason why the attribute can't be set, namely that it's on a property without a setter?
I have no objection to changing the error message, I'm sure it's a small enough change that you should just open a ticket on b.p.o. for it. But I don't expect that it will be particularly useful either. If you can't set an attribute on an object, aren't there three obvious causes to check? - the object has no __dict__, and so has no attributes at all; e.g. trying to set an attribute on a float; - the object has slots, but 'f' is not one of them; - or 'f' is a property with no setter (or a setter that raises AttributeError). Have I missed any common cases? The error messages are different in each case, but even if they were the same, there are three obvious causes to check, and property is one of them. I suppose that there are exotic failures that could happen in __getattribute__ or weird descriptors, or metaclass magic, etc, and *those* might be hard to debug, but it doesn't seem likely to me that a read-only property will be mysterious. Especially if you have the object available in the interactive interpreter, so you can inspect it with the various tools available: * dir(obj) * help(obj) * type(obj).f # Will display <property object at 0x...> etc. Its hard to keep the existence of a property secret in Python. So once you know that f is a property, it might be hard to work out which of the fifty-seven superclasses and mixins it came from *wink* but that's neither here nor there :-) Maybe reporting "can't set property 'f'" is good enough. -- Steve

I have to say I agree with Neil here. I was trying to think about what other reasons an attribute might be unsettable, and did not quickly come up with that list. And after reading your list, I tried to imagine what I’d tell my beginning students ! But if the error message is indeed unique then yes: "can't set property 'f'" Would be far more clear message. -CHB -- Christopher Barker, PhD (Chris) Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython

On Fri, Feb 11, 2022 at 08:00:54AM -0800, Christopher Barker wrote:
I'm curious what list you came up with if it excluded the three cases I suggested (no __dict__, __slots__, property). What other cases are there? There's dynamic attributes with __getattr__ and __getattribute__, and who knows what can be done with metaclass magic, but I'm impressed and kinda intimidated if they are what came to mind *before* objects without a dict :-)
And after reading your list, I tried to imagine what I’d tell my beginning students !
Tell them in what context? Have you already covered properties? If so, then it should be fairly obvious what to tell them: "Class, what do you think will happen if you have a property with no setter and you try to set its value?" If your class is experienced enough to have learned about classes and properties, they will surely guess some sort of exception will happen.
As I said, I don't object to the change in wording if it is easy enough to implement. -- Steve

On Sat, Feb 12, 2022 at 5:32 PM Steven D'Aprano <steve@pearwood.info> wrote:
You're giving me far too much credit -- I hadn't thought about it that much but "no __dict__" was all I cam up with quickly -- and I had no idea what other possibilities there were. (Once I was reminded of__slots__)
That's the easy part -- we cover that when we cover properties -- this whole thing is not an issue when you are working with your own code that you just added a property to. This issue Neil brought up is that if teh cause is p the chain of superclasses somewhere, you don't get much help from the error message. Telling newbies that that means that it's either a property with no setter, or am object without a __dict__, or one with __slots__ defined is not really very helpful.
I think we're all on the same page here. -CHB -- Christopher Barker, PhD (Chris) Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython

On 2/13/22, Christopher Barker <pythonchb@gmail.com> wrote:
The __slots__ case is due to the lack of a __dict__ slot. It can be manually added in __slots__ (though adding __dict__ back is uncommon), along with the __weakref__ slot. The exception message when there's no __dict__ is generally good enough. For example: >>> (1).x = None Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'int' object has no attribute 'x' It's clear that the object has no __dict__ and no descriptor named "x". However, the message gets confusing with partially implemented magic attributes. For example, implement __getattr__(), but not __setattr__() or __delattr__(): class C: __slots__ = () def __getattr__(self, name): class_name = self.__class__.__name__ if name == 'x': return 42 raise AttributeError(f'{class_name!r} object has no ' f'attribute {name!r}') >>> c = C() >>> c.x 42 >>> c.x = None Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'C' object has no attribute 'x' Add __setattr__(): def __setattr__(self, name, value): class_name = self.__class__.__name__ if name == 'x': raise AttributeError(f'attribute {name!r} of {class_name!r} ' 'objects is not writable') raise AttributeError(f'{class_name!r} object has no ' f'attribute {name!r}') >>> c = C() >>> c.x = None Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 12, in __setattr__ AttributeError: attribute 'x' of 'C' objects is not writable >>> del c.x Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'C' object has no attribute 'x' Add __delattr__(): def __delattr__(self, name): class_name = self.__class__.__name__ if name == 'x': raise AttributeError(f'attribute {name!r} of {class_name!r} ' 'objects is not writable') raise AttributeError(f'{class_name!r} object has no ' f'attribute {name!r}') >>> c = C() >>> c.x 42 >>> c.x = None Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 12, in __setattr__ AttributeError: attribute 'x' of 'C' objects is not writable >>> del c.x Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 19, in __delattr__ AttributeError: attribute 'x' of 'C' objects is not writable

Yeah, I have to agree with Neil. I had exactly this issue a couple years ago, and it took me an hour or two to figure out that it was a property/descriptor-protocol causing the issue, at which point the fix became trivial. Just knowing to think "it's a property, stupid!" was the hard part and just stating that in the error message would have saved me the frustration. On Fri, Feb 11, 2022 at 2:46 AM Neil Girdhar <mistersheik@gmail.com> wrote:

On Fri, 11 Feb 2022 at 18:36, Eric Fahlgren <ericfahlgren@gmail.com> wrote:
Yeah, I have to agree with Neil. I had exactly this issue a couple years ago, and it took me an hour or two to figure out that it was a property/descriptor-protocol causing the issue, at which point the fix became trivial. Just knowing to think "it's a property, stupid!" was the hard part and just stating that in the error message would have saved me the frustration.
I'm inclined to say just raise an issue on bpo. If it's easy enough, it'll just get done. If it's hard, having lots of people support the idea won't make it any easier. I don't think this is something that particularly needs evidence of community support before asking for it. Paul

On 2/11/22, Paul Moore <p.f.moore@gmail.com> wrote:
The error message is in property_descr_set() in Objects/descrobject.c. I agree that it should state that the attribute is a property. Python developers know that a property requires a getter, setter, and deleter method in order to function like a regular, mutable attribute. If not, help(property) explains it all clearly.
participants (7)
-
André Roberge
-
Christopher Barker
-
Eric Fahlgren
-
Eryk Sun
-
Neil Girdhar
-
Paul Moore
-
Steven D'Aprano