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

Tym*_*tts 5 python docstring metaclass class-method

我的问题:

我创建了一系列节点,每个节点都有一组与之关联的属性对象.使用每个属性的描述和名称初始化属性对象.我希望这些属性及其描述能够在我的sphinx文档中显示,而不必在两个地方维护名称/描述,一次在类的doc字符串中,一次在属性的初始化中.

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

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__
Run Code Online (Sandbox Code Playgroud)

我希望Foo.attributes_string的结果显示在Foo的doc字符串中,以便我得到这个:

My doc string

Default Attributes:
foo: description of foo attribute
bar: description of bar attribute
Run Code Online (Sandbox Code Playgroud)

我的解决方案尝试:

首先我想"嘿,这很简单!我只是设置一个类装饰器!":

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
    """
Run Code Online (Sandbox Code Playgroud)

这失败了,出现以下错误:

AttributeError: attribute '__doc__' of 'type' objects is not writable
Run Code Online (Sandbox Code Playgroud)

所以我想"那么我会在创建类之前使用元类来设置__doc__!".当我去实现这个时,我立即遇到了一个问题:你如何调用一个尚未创建的类的类方法?

我通过一个非常讨厌的解决方法来避免这个问题让我感到畏缩:我创建了两次类,一次没有修改它,所以我可以调用它的classmethod,然后再次使用正确的__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
Run Code Online (Sandbox Code Playgroud)

这完全有效,并给了我正在寻找的结果:

My doc string

Default Attributes:
foo: description of foo attribute
bar: description of bar attribute
Run Code Online (Sandbox Code Playgroud)

但是,创建课程两次是不是非常低效?有关系吗?有没有更好的办法?我正在努力做的事真的很蠢吗?

met*_*ter 5

有点四处寻找你,没有骰子,因为它从根本上被破坏了,直到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
Run Code Online (Sandbox Code Playgroud)

不需要屠杀该__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
Run Code Online (Sandbox Code Playgroud)

在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()
Run Code Online (Sandbox Code Playgroud)

你的结果是:

class Foo(__builtin__.object)
 |  My doc string
 |      
 |  Default Attributes:
 |  foo: description of foo attribute
 |  bar: description of bar attribute
Run Code Online (Sandbox Code Playgroud)