Sphinx文档模块属性

11

我有一个模块应该具有一个@property,我通过将类设置为模块来解决了这个问题。 我从这个答案得到了灵感:Lazy module variables--can it be done?

我希望这是可重复且易于使用的,因此我为此制作了一个元类。 这非常好用。

问题在于,在使用Sphinx生成文档时,属性不会被记录。 其他所有内容都按预期记录。 我不知道如何解决这个问题,也许这是Sphinx的问题?

该模块:

import sys
import types

class ClassAsModule(type):
    def __new__(cls, name, bases, attrs):
        # Make sure the name of the class is the module name.
        name = attrs.pop('__module__')
        # Create a class.
        cls = type.__new__(cls, name, bases, attrs)
        # Instantiate the class and register it.
        sys.modules[name] = cls = cls(name)
        # Update the dict so dir works properly
        cls.__dict__.update(attrs)

class TestClass(types.ModuleType):
    """TestClass docstring."""
    __metaclass__ = ClassAsModule
    @property
    def some_property(self):
        """Property docstring."""
        pass
    def meth():
        """meth doc"""
        pass

使用复制粘贴来生成/查看Sphinx文档:

sphinx-apidoc . -o doc --full
sphinx-build doc html
xdg-open html/module.html

最重要的部分是记录类的属性。如果还记录原始模块成员,则会加分。

编辑:应该将该类作为其所在的模块进行文档化。该类是这样使用的,因此在Sphinx中应以这种方式显示。

所需输出示例:

Module Foo
    TestClass docstring.

    some_property
        Property docstring.

    meth()
        meth doc

编辑2: 我找到了一些可能有助于找到解决方案的东西。当一个常规模块foo的内容如下:

#: Property of foo
prop = 'test'

Sphinx 对此进行了文档化:

foo.prop = 'test'
    Property of foo

如果 prop 是类的属性之一,那么同样适用。 我还没有弄清楚为什么它在我的特殊情况下不起作用。

你的代码无法运行。ModMeta未定义。你能否提供可运行的代码? - jterrace
@jterrace 复制粘贴失败。现在已经修复了;-) - siebz0r
你确定这个程序按照你想的那样工作吗?方法/属性似乎无法访问模块命名空间。例如,如果我将meth()中的pass语句替换为return sys.path,我会得到这个错误:AttributeError: 'NoneType' object has no attribute 'path'。如果只返回“Hello”之类的东西,它可以正常工作,但不能访问应该是全局的内容。 - Jacinda
你是否看到和我一样的行为? - Jacinda
我认为这与问题的根源有关,但我还没有完全理清所有的关联。 - Jacinda
显示剩余2条评论
2个回答

2
这是我的理解。
理论是:通过这种(有点破坏性的)方式使一个类像模块一样运作,让sphinx认为它不需要(解析)模块属性(因为它是一个类级别的范式)。所以,对于sphinx来说,TestClass是一个模块。
首先,为了确保罪魁祸首是使一个类像模块一样运作的代码 - 让我们把它删除:
class ClassAsModule(type):
    pass

我们将在文档中看到:
package Package
    script Module

    class package.script.ClassAsModule
        Bases: type

    class package.script.TestClass
        Bases: module

        TestClass docstring.

        meth()
            meth doc

        some_property
            Property docstring.

如你所见,Sphinx没有出现任何问题就读取了属性。这里没有什么特别之处。


解决你的问题的可能方法是避免使用@property装饰器,而是使用调用property类构造函数。例如:

import sys
import types

class ClassAsModule(type):
    def __new__(cls, name, bases, attrs):
        # Make sure the name of the class is the module name.
        name = attrs.pop('__module__')
        # Create a class.
        cls = type.__new__(cls, name, bases, attrs)
        # Instantiate the class and register it.
        sys.modules[name] = cls = cls(name)
        # Update the dict so dir works properly
        cls.__dict__.update(attrs)


class TestClass(types.ModuleType):
    """TestClass docstring."""
    __metaclass__ = ClassAsModule

    def get_some_property(self):
        """Property docstring."""
        pass

    some_property = property(get_some_property)

    def meth(self):
        """meth doc"""
        pass

对于这段代码,sphinx 生成的文本如下:
package Package
    script Module
        TestClass docstring.

            package.script.get_some_property(self)
                Property docstring.

            package.script.meth(self)
                meth doc

也许这个答案听起来有些荒唐,但我希望它能指引您走向正确的方向。

遗憾的是,这忽略了属性使用和文档同步的重点。 - siebz0r
是的,但是,重要的是要理解@property装饰器只是一种语法糖。通过实例化property类来定义属性,就像我建议的那样,实际上与使用装饰器的方式相同。但是,在这种情况下,文档显示的图片略有不同。 - alecxe
我授予您赏金,因为否则没有人会获得这些积分。我不会得到退款,也不指望很快有一个被接受的答案,所以这似乎是“最好”的选择。 - siebz0r
谢谢!你认为如果我在这个问题上开始悬赏,这会公平吗?还是我们应该再等一下? - alecxe
我对任何选择都很满意。;-)上次的悬赏并没有达到预期的结果,所以我打算等待。 - siebz0r
好的,让我们稍等一会儿。告诉我什么时候开始,我就会发起悬赏 - 这似乎很公平。谢谢。 - alecxe

0
我发现最好的方法是保持文件内容与编写常规模块时相同,然后在结尾处替换sys.modules中的未成熟模块。
"""Module docstring.  """

import sys
import types

def _some_property(self):
    pass
some_property = property(_some_property)
"""Property docstring."""

def meth():
    """meth doc"""
    pass

def _make_class_module(name):
    mod = sys.modules[name]
    cls = type('ClassModule', (types.ModuleType,), mod.__dict__)
    clsmod = cls(name)
    clsmod.__dict__.update(mod.__dict__)
    clsmod.__wrapped__ = mod
    sys.modules[name] = clsmod
_make_class_module(__name__)

文本文档:

mymod Module
************

Module docstring.

mymod.meth()

   meth doc

mymod.some_property = None

   Property docstring.

对于我正在使用的Sphinx版本(v1.1.3),看起来你必须显式地应用属性构造函数(不能将其用作装饰器),并且文档字符串必须放在文件的顶层,在创建属性的构造函数调用后的下一行(它不能作为属性getter内部的文档字符串)。源代码仍然相当易读。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接