python ≥3.9 中的只读类属性,支持 `help()`
Posted
技术标签:
【中文标题】python ≥3.9 中的只读类属性,支持 `help()`【英文标题】:Read-only class properties in python ≥3.9 with `help()` support 【发布时间】:2021-11-24 07:23:04 【问题描述】:从 python 3.9 开始,支持堆叠 @property
和 @classmethod
装饰器。但是,我很难创建一个显示在help
中Readonly properties
部分下的类属性。 [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)
【问题讨论】:
是否可以缓存计算结果?还是必须在每次调用时重新计算? @wim 我稍后已经将它与缓存装饰器结合使用。但是,这意味着当它应该执行 0 次时,它仍然至少执行一次,就像expensive_instance_property
一样。
好的,所以主要问题实际上并不是多次调用,而是自动文档调用了属性代码?
来自 OP 的错误报告:bugs.python.org/issue45356
【参考方案1】:
使用调用堆栈,您可以检测该属性是否被 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 的调用。如果您不喜欢将短路代码直接添加到函数体中,则可以将此想法分解为装饰器。
【讨论】:
以上是关于python ≥3.9 中的只读类属性,支持 `help()`的主要内容,如果未能解决你的问题,请参考以下文章
Objective-C:(私有/公共属性)为外部类调用设置只读属性,为自调用设置只读属性