关于python:如何使用classmethods修改类docstring

How to modify class docstrings using classmethods

我的问题:

我已经创建了一系列节点,每个节点都有一组与它们相关联的属性对象。属性对象是用每个属性的描述和名称初始化的。我希望这些属性及其描述能够显示在我的sphinx文档中,而不必在两个位置维护名称/描述,一个在类的文档字符串中,另一个在属性的初始化中。

要说明问题,请考虑以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 = '
Default Attributes:
'

        for key, value in attributes.iteritems():
            result += '%s: %s
'
% (key, value)
        return result

print Foo.__doc__

我希望foo.attributes_string的结果显示在foo的文档字符串中,这样我可以得到:

1
2
3
4
5
My doc string

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

我的解决方案尝试:

首先我想"嘿,那很简单!我来安排一个班级装饰员!":

1
2
3
4
5
6
7
8
9
10
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
   """

此操作因以下错误而失败:

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

于是我想,"好吧,那我就用一个元类在类被创建之前设置好文档!".当我开始实现这一点时,我立即遇到了一个问题:对于尚未创建的类,如何调用ClassMethod?

我通过一个让我感到不安的非常简单的变通方法规避了这个问题:我创建了两次类,一次不修改它,这样我就可以称它为ClassMethod,然后再次使用适当的文档创建类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

这完全有效,并且给了我我想要的结果:

1
2
3
4
5
My doc string

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

然而,两次创建类不是非常低效吗?重要吗?有更好的方法吗?我想做的事真的很蠢吗?


做了一些四处寻找你,没有骰子,因为它是一个基本上打破,并没有修复,直到Python3.3。因此,如果您计划发布大于3.3的程序,那么__doc__属性将是可变的。

然而,这似乎对您没有帮助,但是有一些方法可以更巧妙地将这个问题解决,使其提交,只需在元类中提供一个__doc__属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 = '
Default Attributes:
'

        for key, value in attributes.items():
            result += '%s: %s
'
% (key, value)
        return result

不需要破坏__new__方法,因为元类中的属性和属性将可用于子类,反之亦然。做一个help(Foo),它现在给出了:

1
2
3
4
5
6
7
8
9
10
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下测试。

注意:它将覆盖声明docstring的标准方法,因此您可能必须将整个内容放在其中,除非您碰巧也覆盖了__new__方法,以使原来的__doc__不受危害。也许这就是你想要的:

1
2
3
4
5
6
7
8
9
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()

你的结果是:

1
2
3
4
5
6
class Foo(__builtin__.object)
 |  My doc string
 |      
 |  Default Attributes:
 |  foo: description of foo attribute
 |  bar: description of bar attribute


如果要为未注释的函数生成docstring,也可以使用pyment。

它不会提供您所期望的特定格式,但目前它会生成以sphinxs、numpydoc或google-doc样式添加(或转换)docstring格式的补丁。