阻止 Sphinx 执行缓存的类方法 属性

Stop Sphinx from Executing a cached classmethod property

我正在编写一个与数据集交互的程序包,代码类似于

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 期间,sphinx 实际上执行了 baseclass_property,这是一个非常昂贵的操作。 (除其他事项外:检查本地是否存在数据集,如果不存在,则下载它,对其进行预处理,计算数据集统计信息,修剪草坪并清除垃圾。)

我注意到,如果我将它设为 MetaClass 属性,则不会发生这种情况,因为 可能是也可能不是错误。通过取消注释这两行将其手动添加到 __dir__ 会导致 sphinx 也处理元类 属性.

问题:

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

一切正常,Sphinx 和 ABC 机制都没有“错误”,语言中更是如此。

Sphinx 使用语言自省功能来检索 class 的成员,然后自省方法。当您将 @class 方法和 @属性 组合在一起时会发生什么,除了它实际上有点令人惊讶之外,当 Sphynx 访问如此创建的 class 成员时,因为它必须在搜索文档字符串时执行,代码被触发并且 运行s.

如果 属性 和 class 方法实际上不能组合使用,实际上也就不足为奇了,因为 propertyclassmethod 装饰器都使用描述符协议来为他们实现的功能使用适当的方法创建一个新对象。

我认为去那里不太令人惊讶的事情是在你的“class方法属性缓存”函数中放置一些明确的保护,以便在处理文件时不运行通过狮身人面像。由于 sphinx 本身没有这个特性,你可以为此使用一个环境变量,比如 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)))

现在,关于在 metaclass 上使用 属性:我建议不要这样做。 Metaclasses 适用于当您确实需要自定义 class 创建过程时,metaclass 上的 property 作为 class 属性 还有。正如您所调查的那样,在这种情况下发生的所有事情是 属性 将被 class' dir 隐藏,因此不会被 Sphinx 内省击中 - 但即使如果您将 metaclass 用于其他目的,如果您只是按照我的建议添加一个守卫,甚至可能 not 阻止 sphinx 正确记录 class 属性,如果它有文档字符串。如果你对 Sphinx 隐藏它,它显然不会被记录。