Since python 3.9, it is supported to stack the @property
and @classmethod
decorators. However, I struggle to create a class-property that shows up in help
under the Readonly properties
section. The solutions presented in [1],[2],[3],[4],[5] didn't solve my problem. Consider:
from time import sleep
from abc import ABC, ABCMeta, abstractmethod
def compute(obj, s):
print(f"Computing {s} of {obj} ...", end="")
sleep(3)
print("DONE!")
return "Phew, that was a lot of work!"
class MyMetaClass(ABCMeta):
@property
def expensive_metaclass_property(cls):
"""This may take a while to compute!"""
return compute(cls, "metaclass property")
class MyBaseClass(ABC, metaclass=MyMetaClass):
@classmethod
@property
def expensive_class_property(cls):
"""This may take a while to compute!"""
return compute(cls, "class property")
@property
def expensive_instance_property(self):
"""This may take a while to compute!"""
return compute(self, "instance property")
class MyClass(MyBaseClass):
"""Some subclass of MyBaseClass"""
help(MyClass)
The issue is that calling help(MyBaseClass)
executes expensive_class_property
multiple times. This caused issues with documentation in the past where for example sphinx
would also end up executing the property code.
Using the metaclass avoids this issue, but has the disadvantage that expensive_metaclass_property
neither shows up in dir(MyClass)
nor help(MyClass)
nor in documentation of MyClass
. How can I get a class-property that shows up in help(MyClass)
under the Readonly properties
section?
Here's what happens when calling help(MyClass)
:
Computing class property of <class '__main__.MyClass'> ...DONE!
Computing class property of <class '__main__.MyClass'> ...DONE!
Computing class property of <class '__main__.MyClass'> ...DONE!
Computing class property of <class '__main__.MyBaseClass'> ...DONE!
Computing class property of <class '__main__.MyClass'> ...DONE!
Help on class MyClass in module __main__:
class MyClass(MyBaseClass)
| Some subclass of MyBaseClass
|
| Method resolution order:
| MyClass
| MyBaseClass
| abc.ABC
| builtins.object
|
| Data and other attributes defined here:
|
| __abstractmethods__ = frozenset()
|
| ----------------------------------------------------------------------
| Class methods inherited from MyBaseClass:
|
| expensive_class_property = 'Phew, that was a lot of work!'
| ----------------------------------------------------------------------
| Readonly properties inherited from MyBaseClass:
|
| expensive_instance_property
| This may take a while to compute!
|
| ----------------------------------------------------------------------
| Data descriptors inherited from MyBaseClass:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)