Making abstract property in Python 3 results in AttributeError
Asked Answered
L

2

21

How do you make an abstract property in python?

import abc
class MyClass(abc.ABC):
    @abc.abstractmethod
    @property
    def foo(self):
        pass

results in the error AttributeError: attribute '__isabstractmethod__' of 'property' objects is not writable

Longlegged answered 23/6, 2022 at 21:28 Comment(0)
L
27

It turns out that order matters when it comes to python decorators.

@abc.abstractmethod
@property

is not the same as

@property
@abc.abstractmethod

The correct way to create an abstract property is:

import abc
class MyClass(abc.ABC):
    @property
    @abc.abstractmethod
    def foo(self):
        pass
Longlegged answered 23/6, 2022 at 21:29 Comment(2)
Why? Could you please describe the reason?Gothicize
I'm afraid I don't know. I just know that if abstractmethod is not the last decorator, it usually causes an error. Python's function grammar documentation doesn't seem to mention anything useful about this docs.python.org/3/reference/…Longlegged
R
2

TL;DR:

cowlinator is correct, order counts.

Formally:

According to the formal documentation for the abstract base class ABC

When abstractmethod() is applied in combination with other method descriptors, it should be applied as the innermost decorator, as shown in the following usage examples:...

(bold is mine for emphasis: not only is ordering important, but abstractmethod must be innermost)

Not-too-satisfactory explanation:

Decorators like @property and @abstractmethod are functions that wrap the functions (methods) they decorate. So two decorators result in nested wrappings, with the one closest to the function definition (the lowest in your editor) being the innermost wrapping function.

In the @property decorator, the __isabstractmethod__ internal attribute is declared as a property itself, with only a getter, no setter - making it readonly. This is from the python code for property in builtins.py

    __isabstractmethod__ = property(lambda self: object(), lambda self, v: None, lambda self: None)  # default

The first arg is the getter, the second the setter - None in this case - see the constructor here if you don't believe me:

   def __init__(self, fget=None, fset=None, fdel=None, doc=None): # known special case of property.__init__

In the @abstractmethod wrapping the __isabstractmethod__ attribute is set to True. See here:

    funcobj.__isabstractmethod__ = True

That's from the abc module, where funcobj is the method being decorated. Now, usually that's your would-be-abstract method, but in this case, it's the property decorator function wrapping the original method. And as we saw above, that function made __isabstractmethod__ readonly.

I say "not-too-satisfactory" because there is no proper justification of this - just obscure documentation that you won't find until you run into the problem. It's really just a limitation of the way decorators work I guess.

For extra background, I tracked this change here in GitHub (gotta love open-source!) which includes the update to these release notes, which state - also without a lot of explanation:

abc.abstractproperty has been deprecated, use property with abc.abstractmethod() instead.

For deep-deep source of the exception you got, you can see the C source code for python descriptors here and search down for 'is not writable' to see where it throws the exception while trying to generate the setter.

Ra answered 11/8, 2023 at 12:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.