如何使用类方法修改类文档字符串

7 投票
2 回答
1741 浏览
提问于 2025-04-18 00:16

我的问题:

我创建了一系列节点,每个节点都有一组属性对象。每个属性对象都有描述和名称。我希望这些属性及其描述能够出现在我的sphinx文档中,而不需要在两个地方维护名称和描述:一次是在类的文档字符串中,另一次是在属性的初始化中。

为了说明这个问题,看看下面的代码:

class Foo(object):
    """My doc string
    """
    @classmethod
    def default_attributes(cls):
        return {'foo':'description of foo attribute',
                'bar':'description of bar attribute'}

    @classmethod
    def attributes_string(cls):
        attributes = cls.default_attributes()
        result = '\nDefault Attributes:\n'
        for key, value in attributes.iteritems():
            result += '%s: %s\n' % (key, value)
        return result

print Foo.__doc__

我希望Foo.attributes_string的结果能出现在Foo的文档字符串中,这样我就能得到这个:

My doc string

Default Attributes:
foo: description of foo attribute
bar: description of bar attribute

我的解决尝试:

首先我想:“这很简单!我只需要设置一个类装饰器!”:

def my_decorator(cls):
    doc = getattr(cls, '__doc__', '')
    doc += cls.attributes_string()
    cls.__doc__ = doc
    return cls

@my_decorator
class Foo(object):
    """My doc string
    """

结果却失败了,出现了以下错误:

AttributeError: attribute '__doc__' of 'type' objects is not writable

然后我想:“那我就用元类在类创建之前设置__doc__!”当我去实现这个时,我立刻遇到了一个问题:怎么调用一个还没创建的类的方法?

我用一个很“hacky”的解决方法绕过了这个问题,这让我觉得很尴尬:我创建了这个类两次,第一次不修改它,以便我可以调用它的类方法,然后再创建一次,给它正确的__doc__:

class Meta(type):
    def __new__(meta_cls, name, bases, cls_dict):
        tmpcls = super(Meta, meta_cls).__new__(meta_cls, name, bases, cls_dict)

        doc = cls_dict.get('__doc__', '')
        doc += tmpcls.attributes_string()
        cls_dict['__doc__'] = doc

        return super(Meta, meta_cls).__new__(meta_cls, name, bases, cls_dict)

class Foo(object):
    """My doc string
    """
    __metaclass__ = Meta

这个方法完全有效,给了我想要的结果:

My doc string

Default Attributes:
foo: description of foo attribute
bar: description of bar attribute

不过,创建类两次是不是效率太低了?这重要吗?有没有更好的方法?我在尝试的事情真的很傻吗?

2 个回答

1

如果你想为没有注释的函数生成文档字符串,可以使用Pyment这个工具。

它可能不会按照你想要的特定格式来生成,但目前可以生成一些补丁,添加(或转换)成Sphinx、Numpydoc或Google文档风格的文档字符串。

6

我帮你查了一下,这个问题是因为某些根本性的错误,直到 Python 3.3 才被修复。所以如果你打算发布的程序是 3.3 以上的版本,__doc__ 属性将是可变的。

不过,这似乎对你没有帮助,但有一些聪明的方法可以解决这个问题,你可以在元类中提供一个 __doc__ 属性。

class Meta(type):

    @property
    def __doc__(self):
        return self.attributes_string()

class Foo(object):
    """My doc string
    """

    __metaclass__ = Meta

    @classmethod
    def default_attributes(cls):
        return {'foo':'description of foo attribute',
                'bar':'description of bar attribute'}

    @classmethod
    def attributes_string(cls):
        attributes = cls.default_attributes()
        result = '\nDefault Attributes:\n'
        for key, value in attributes.items():
            result += '%s: %s\n' % (key, value)
        return result

你不需要修改那个 __new__ 方法,因为元类中的属性和属性在子类中都是可以使用的,反之亦然。你可以运行 help(Foo),现在它会显示这个:

CLASSES
    __builtin__.object
        Foo
    __builtin__.type(__builtin__.object)
        Meta

    class Foo(__builtin__.object)
     |  Default Attributes:
     |  foo: description of foo attribute
     |  bar: description of bar attribute

在 Python 2.7 下测试过。

注意:这会覆盖标准的文档字符串声明方式,所以你可能需要把整个内容放进去,除非你也重写 __new__ 方法,把原来的 __doc__ 放到安全的地方。也许这正是你想要的:

class Meta(type):

    def __new__(cls, name, bases, attrs):
        attrs['_doc'] = attrs.get('__doc__', '')
        return super(Meta, cls).__new__(cls, name, bases, attrs)

    @property
    def __doc__(self):
        return self._doc + self.attributes_string()

你的结果:

class Foo(__builtin__.object)
 |  My doc string
 |      
 |  Default Attributes:
 |  foo: description of foo attribute
 |  bar: description of bar attribute

撰写回答