Reputation: 26901
I've seen many answers to that question, all of them saying it's impossible to use a property with a classmethod, but the following code works:
class A:
@classmethod
@property
def hello(cls):
print(1234)
>>> A.hello
1234
Why and how does it work?
Running on CPython 3.9.1.
Upvotes: 0
Views: 1293
Reputation: 26901
Starting in Python 3.9, classmethods trigger the descriptor protocol. From the Python docs:
The code path for
hasattr(obj, '__get__')
was added in Python 3.9 and makes it possible forclassmethod()
to support chained decorators.
Surprisingly, diving a bit deeper into the subject will show you that classmethod
triggers the descriptor __get__
with the class itself as the instance:
class Descriptor:
def __get__(self, instance, owner):
print(instance, owner)
def __set__(self, value, owner):
print(value, owner)
class A:
regular = Descriptor()
clsmethod = classmethod(Descriptor())
>>> A.regular
None <class '__main__.A'>
>>> A.clsmethod
<class '__main__.A'> None
I'm guessing they made it specifically to support descriptors such as @property
, as accessing them through the class returns the property itself:
class B:
@property
def prop(self):
print(self)
>>> B.__dict__["prop"].__get__(None, 1234)
<property object at 0x000001BEEB635630>
>>> B.__dict__["prop"].__get__(1234, None)
1234
It's a bit unintuitive and turns the descriptor protocol clunky if you wish to support both classmethod
and normal descriptors, as you have to check if the owner
is None
.
Keep in mind however, __set__
is not called (as the descriptor protocol doesn't call it when setting class attributes), making you unable to use @property.setter
:
>>> A.regular = 1234
>>> A.regular
1234
>>> A.clsmethod = 1234
>>> A.clsmethod
1234
Upvotes: 1