微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决阻止 Sphinx 执行缓存的类方法属性

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

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 property does not appear in the classes __dir__ call 可能是也可能不是错误。通过取消注释这两行将其手动添加__dir__ 会导致 sphinx 也处理元类属性

问题:

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

解决方法

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

Sphinx 使用语言自省功能来检索类的成员,然后自省方法。当你结合 @classmethod 和 @property 时会发生什么,除了它有点作为一个很好的惊喜实际工作之外,当这样创建的类成员被 Sphynx 访问时,它必须在搜索文档字符串时执行,代码被触发并运行。

如果实际上不能组合使用 property 和 classmethod 实际上就不那么令人惊讶了,因为 propertyclassmethod 装饰器都使用描述符协议来创建具有适当方法的新对象他们实施。

我认为不那么令人惊讶的事情是在您的“类方法属性缓存”函数中放置一些显式保护,以便在 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 也几乎是偶然地用作类属性。在这种情况下发生的所有事情,正如您所调查的那样,该属性将对类“dir其他目的,如果你只是像我建议的那样添加一个守卫,甚至可能不会阻止 sphinx 正确记录类属性,如果它有一个文档字符串。如果你对 Sphinx 隐藏它,它显然不会被记录。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。