从python 3.9开始,支持堆叠@property
和@classmethod
装饰符。然而,我努力创建一个在Readonly properties
节下的help
中出现的类属性。[1]、[2]、[3]、[4]、[5]给出的解决方案并不能解决我的问题。考虑:
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)
问题是调用help(MyBaseClass)
会多次执行expensive_class_property
。这在过去的文档中造成了问题,例如sphinx
最终也会执行属性代码。
使用元类避免了这个问题,但缺点是expensive_metaclass_property
既没有出现在dir(MyClass)
、help(MyClass)
中,也没有出现在MyClass
的文档中。我怎么能得到一个类属性,显示在help(MyClass)
下的Readonly properties
节?
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)
使用调用堆栈,您可以检测属性是否被pydoc调用并且在这种情况下短路:
import inspect
class A:
@classmethod
@property
def expensive_class_property(cls):
"""This may take a while to compute!"""
fnames = [f.filename for f in inspect.getouterframes(inspect.currentframe())]
if any(fname.endswith("pydoc.py") for fname in fnames):
return "Text returned for autodoc"
print("computing class property")
return "Phew, that was a lot of work!"
这个帮助看起来像这样:
Help on class A in module __main__:
class A(builtins.object)
| Class methods defined here:
|
| expensive_class_property = 'Text returned for autodoc'
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
但正常访问将如预期:
>>> A.expensive_class_property
computing class property
'Phew, that was a lot of work!'
这并不阻止help(A)
多次调用描述符,但是你可能无法防止这种无需修改直接pydoc.py
。只要短路是快速的,那么你可能无论如何都不需要阻止它。
该方法应该易于扩展以检测Sphinx的调用。如果您不喜欢将短路代码直接添加到函数体中,则可以将此想法分解为装饰器。