如何使用类方法修改类文档字符串
我的问题:
我创建了一系列节点,每个节点都有一组属性对象。每个属性对象都有描述和名称。我希望这些属性及其描述能够出现在我的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 个回答
如果你想为没有注释的函数生成文档字符串,可以使用Pyment这个工具。
它可能不会按照你想要的特定格式来生成,但目前可以生成一些补丁,添加(或转换)成Sphinx、Numpydoc或Google文档风格的文档字符串。
我帮你查了一下,这个问题是因为某些根本性的错误,直到 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