A small add-on to python 3.9 was that
Changed in version 3.9: Class methods can now wrap other descriptors such as property().
Which is useful in several contexts, for example as a solution for Using property() on classmethods or in order to create what is essentially a lazily evaluated class-attribute via the recipe
class A:
@classmethod
@property
@cache
def lazy_class_attribute(cls):
"""Method docstring."""
return expensive_computation(cls)
This works well and fine within python, importing the class, instantiating it or subclasses of it will not cause expensive_computation
to occur, however it seems that both pydoc
and sphinx
will not only cause execution expensive_computation
when they try to obtain the docstring, but not display any docstring for this @classmethod
whatsoever.
Question: Is it possible - from within python (*) - to have lazily evaluated class-attributes/properties that do not get executed when building documentation?
(*) One workaround presented in Stop Sphinx from Executing a cached classmethod property, thanks to /u/jsbueno, consists of modifying the function body based on an environment variable:
def lazy_class_attribute(cls):
"""Method docstring."""
if os.environ.get("GENERATING_DOCS", False):
return
return expensive_computation(cls)
I like this workaround a lot, since in particular, it allows one to present a different output when documenting. (For example, my classes have attributes which are paths based on the class-name. If a different user executes the same script, the path will be different since their home folder is different.)
There are 2 problems, however:
- This approach depends on doing things outside of python that, imo, should, in an ideal world, not be necessary to do outside of python itself
- To get the docstring in documentation, it seems we end up having to do something unintuitive like
class MetaClass:
@property
@cache
def _expensive_function(cls):
"""some expensive function"""
class BaseClass(metaclass=MetaClass):
lazy_attribute: type = classmethod(MetaClass._expensive_funcion)
"""lazy_attribute docstring"""
PS: By the way, is there any functional difference between a @classmethod@property
and an attribute
? They seem very similar. The only difference I can make out at the moment is that if the attribute needs access to other @classmethod
s, we need to move everything in the metaclass as above.