【问题标题】:Stop Sphinx from Executing a cached classmethod property阻止 Sphinx 执行缓存的 classmethod 属性
【发布时间】:2021-09-20 23:38:49
【问题描述】:

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

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。

【问题讨论】:

    标签: python properties python-sphinx metaclass python-3.9


    【解决方案1】:

    一切正常,Sphinx 中没有“错误”,ABC 机器中也没有,语言中甚至更少。

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

    如果属性和类方法实际上不能组合使用,这实际上就不会那么令人惊讶了,因为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 内省的影响 - 但即使您将元类用于其他目的,如果你只是像我建议的那样添加一个守卫,如果它有一个文档字符串,甚至可能 not 阻止 sphinx 正确记录类属性。如果你对 Sphinx 隐藏它,它显然会没有记录。

    【讨论】:

    • 非常感谢您的回答,使用环境变量技巧就像一个魅力。不幸的是,似乎还有另一个问题:Sphinx 似乎无论如何都没有记录@classmethod@property,我猜我将不得不打开一个错误报告并同时使用元类版本。能够组合这两个装饰器仅在 python 3.9 中引入。至于我为什么使用元类:我用它来初始化基于cls.__name__的某些类属性,即我在元类__init__中有cls.dataset_path= basepath.joindir(cls.__name__)之类的东西。
    猜你喜欢
    • 2012-06-15
    • 1970-01-01
    • 1970-01-01
    • 2016-08-19
    • 1970-01-01
    • 2018-09-03
    • 2021-09-20
    • 2015-08-20
    • 1970-01-01
    相关资源
    最近更新 更多