阻止Sphinx执行缓存的类方法属性
问题描述
我正在编写一个与DataSet交互的包,代码类似
from abc import ABC, ABCMeta, abstractmethod
from functools import cache
from pathlib import Path
from warnings import warn
class DatasetMetaClass(ABCMeta):
r"""Meta Class for Datasets"""
@property
@cache
def metaclass_property(cls):
r"""Compute an expensive property (for example: dataset statistics)."""
warn("Caching metaclass property...")
return "result"
# def __dir__(cls):
# return list(super().__dir__()) + ['metaclass_property']
class DatasetBaseClass(metaclass=DatasetMetaClass):
r"""Base Class for datasets that all datasets must subclass"""
@classmethod
@property
@cache
def baseclass_property(cls):
r"""Compute an expensive property (for example: dataset statistics)."""
warn("Caching baseclass property...")
return "result"
class DatasetExampleClass(DatasetBaseClass, metaclass=DatasetMetaClass):
r"""Some Dataset Example."""
现在,问题是在make html
期间,狮身人面像实际上执行baseclass_property
,这是一个非常昂贵的操作。(除其他事项外:检查本地是否存在数据集,如果不存在,则下载它、对其进行预处理、计算数据集统计数据、修剪草坪和倒垃圾。)
我注意到,如果我将其设置为MetaClass属性,则不会发生这种情况,因为meta-class property does not appear in the classes __dir__
call可能是错误,也可能不是错误。通过取消对这两行的注释将其手动添加到__dir__
会导致狮身人面像也处理元类属性。
问题:
- 这是Sphinx中的错误吗?鉴于
@properties
通常处理得很好,似乎无意中将其中断为@classmethod@property
。 - 目前避免这个问题的最佳选择是什么?我可以以某种方式告诉Sphinx不要解析这个函数吗?我希望可以通过类似于
# noqa
、# type: ignore
、# pylint disable=
等的注释或通过某种@nodoc
修饰符来禁用函数的狮身人面像。
解决方案
一切正常,Sphinx和abc机器中都没有错误,语言中的错误更少。
Sphinx使用语言自省功能来检索类的成员,然后自省方法。当您将@类方法和@Property组合在一起时会发生的情况是,除了在某种程度上令人惊讶地实际工作之外,当Sphnx访问这样创建的类成员时,就像它在搜索文档字符串时必须做的那样,代码被触发并运行。
如果属性和类方法实际上不能组合使用,这实际上并不令人惊讶,因为property
和classmethod
修饰符都使用描述符协议来创建一个新对象,该对象具有它们实现的功能的适当方法。
我认为更不令人惊讶的事情是在您的&类方法属性缓存&函数中设置一些明确的保护措施,使其在文件被Shinx处理时不会运行。由于Shinx本身没有此功能,因此您可以使用环境变量来实现此功能,比如GENERATING_DOCS
。(这不存在,它可以是任何名称),然后是方法内部的保护,如:
...
def baseclass_property(self):
if os.environ.get("GENERATING_DOCS", False):
return
然后您可以在运行脚本之前手动设置该变量,或者在Sphinx‘conf.py
文件本身中设置它。
如果您有几个这样的方法,并且不想在所有这些方法中都编写保护代码,那么您可以做一个修饰符,同时,只需使用相同的修饰符来同时应用其他3个修饰符:
from functools import cache, wraps
import os
def cachedclassproperty(func):
@wraps(func)
def wrapper(*args, **kwargs):
if os.environ.get("GENERATING_DOCS", False):
return
return func(*args, **kwargs)
return classmethod(property(cache(wrapper)))
现在,至于在元类上使用属性:我建议不要使用它。元类是在您确实需要定制类创建过程时使用的,几乎可以偶然地将元类上的property
用作类属性。正如您所调查的,在这种情况下,属性将对类‘dir
隐藏,因此不会受到Sphinx自检的影响--但即使您将元类用于其他目的,如果您像我所建议的那样简单地添加一个警卫,而不是可能会阻止狮身人面像正确地记录类属性,如果它有文档字符串的话。如果你对狮身人面像隐藏它,它显然就不会被记录下来。
相关文章