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

问题描述 投票:0回答:1

我正在编写一个用于与数据集交互的包,并且代码看起来像这样

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 属性,则不会发生这种情况,因为 meta-class 属性不会出现在类

__dir__
call 中,这可能是也可能不是一个错误。通过取消注释这两行来手动将其添加到
__dir__
会使 sphinx 也处理元类属性。

问题:

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

一切都按其应有的方式运行,Sphinx 中没有“bug”,ABC 机器中也没有“bug”,语言中更不用说。

Sphinx 使用语言自省功能来检索类的成员,然后内省方法。当你组合 @classmethod 和 @property 时会发生什么,除了它实际上起作用之外,当这样创建的类成员被 Sphynx 访问时,就像它在搜索文档字符串时必须做的那样,代码被触发并运行。

如果属性和类方法实际上不能组合使用,那其实并不奇怪,因为

property
classmethod
装饰器都使用描述符协议来创建一个新对象,并为其实现的功能提供适当的方法。 (更新
property
classmethod
的交互实际上足够复杂,从Python 3.12开始不再支持这种模式)

我认为不太令人惊讶的事情是在“类方法属性缓存”函数中放置一些显式保护,以便在 sphinx 处理文件时不运行。由于 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)))

现在,至于使用元类上的属性:我建议不要这样做。元类适用于您确实需要自定义类创建过程的情况,并且元类上的

property
几乎偶然也可以用作类属性。正如您所调查的,在这种情况下发生的所有事情都是该属性将从类中隐藏,因此不会受到 Sphinx 内省的影响 - 但即使您将元类用于其他目的,如果您只是按照我的建议添加一个防护,甚至可能
not
阻止sphinx正确记录类属性(如果它有文档字符串)。如果你向 Sphinx 隐藏它,它显然不会被记录下来。

© www.soinside.com 2019 - 2024. All rights reserved.