python≥3.9支持' help() '的只读类属性



从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的调用。如果您不喜欢将短路代码直接添加到函数体中,则可以将此想法分解为装饰器。

最新更新