Reputation: 13
I'm trying to create a property on a abstract class. Instead of stacking @property
on top of @abstractmethod
on top of the getter, I'm trying to create the property in the abstract base class manually with the getter as input, which I've decorated with @abstractmethod
.
With the former approach you need to decorate the getter with @property
again in the subclass. Whereas the latter approach is slightly nicer for me since now the subclass only needs to define a getter method and accessing the property should work. However, it doesn't seem to work as expected.
import abc
class Foo(abc.ABC):
@abc.abstractmethod
def _get_bar(self) -> bool:
...
# not preferred.
# @property
# @abc.abstractmethod
# def BAR(self) -> bool:
# ...
# works!
# @property
# def BAR(self) -> bool:
# return self._get_bar()
# doesn't work!
BAR = property(_get_bar)
class FooBar(Foo):
# not preferred.
# @property
# def BAR(self) -> bool:
# ...
def _get_bar(self) -> bool:
return True
print(FooBar().BAR) # TypeError: Can't instantiate abstract class FooBar with abstract methods BAR
I'm basing this off what is written in the docs about @abc.abstractmethod
, the last bit of the code snippet. Or at least I think I am. But it looks like I'm doing something wrong or not understanding properties that well.
Upvotes: 1
Views: 91
Reputation: 110506
The difference is that this code
BAR = property(_get_bar)
is executed only once when the body of your base class (Foo
) is executed -
the _getbar
in this expression refers to the method defined in this class (Foo), which is abstract and empty.
While the form
@property
def BAR(self) -> bool:
return self._get_bar()
encapsulates the reference to _get_bar
inside a method (BAR), which will receive the instance as a parameter, and then retrieve the method from whatever subclass from Foo
the instance (self
) happens to be.
The way to fix that is to wrap the property argument in the function call form as a lambda, so that the method is retrieved from the appropriate class on call time:
BAR = property(lambda self: self._get_bar())
Upvotes: 1