阻止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__会导致狮身人面像也处理元类属性。

问题:

  1. 这是Sphinx中的错误吗?鉴于@properties通常处理得很好,似乎无意中将其中断为@classmethod@property
  2. 目前避免这个问题的最佳选择是什么?我可以以某种方式告诉Sphinx不要解析这个函数吗?我希望可以通过类似于# noqa# type: ignore# pylint disable=等的注释或通过某种@nodoc修饰符来禁用函数的狮身人面像。

解决方案

一切正常,Sphinx和abc机器中都没有错误,语言中的错误更少。

Sphinx使用语言自省功能来检索类的成员,然后自省方法。当您将@类方法和@Property组合在一起时会发生的情况是,除了在某种程度上令人惊讶地实际工作之外,当Sphnx访问这样创建的类成员时,就像它在搜索文档字符串时必须做的那样,代码被触发并运行。

如果属性和类方法实际上不能组合使用,这实际上并不令人惊讶,因为propertyclassmethod修饰符都使用描述符协议来创建一个新对象,该对象具有它们实现的功能的适当方法。

我认为更不令人惊讶的事情是在您的&类方法属性缓存&函数中设置一些明确的保护措施,使其在文件被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自检的影响--但即使您将元类用于其他目的,如果您像我所建议的那样简单地添加一个警卫,而不是可能会阻止狮身人面像正确地记录类属性,如果它有文档字符串的话。如果你对狮身人面像隐藏它,它显然就不会被记录下来。

相关文章