在Python中inheritance方法的文档

我有一个面向对象的层次结构,它的代码本身需要尽可能多的维护。 例如,

class Swallow(object): def airspeed(self): """Returns the airspeed (unladen)""" raise NotImplementedError class AfricanSwallow(Swallow): def airspeed(self): # whatever 

现在,问题是AfricanSwallow.airspeed不会inheritance超类方法的文档string。 我知道我可以使用模板方法模式,即保持文档string

 class Swallow(object): def airspeed(self): """Returns the airspeed (unladen)""" return self._ask_arthur() 

并在每个子类中实现_ask_arthur 。 但是,我想知道是否有另一种方式来让文档被inheritance,也许还有一些我还没有发现的装饰器呢?

用类装饰器风格写一个函数来为你做拷贝。 在Python2.5中,可以在创build类之后直接应用它。 在以后的版本中,您可以使用@decorator表示法。

下面是关于如何做的第一步:

 import types def fix_docs(cls): for name, func in vars(cls).items(): if isinstance(func, types.FunctionType) and not func.__doc__: print func, 'needs doc' for parent in cls.__bases__: parfunc = getattr(parent, name, None) if parfunc and getattr(parfunc, '__doc__', None): func.__doc__ = parfunc.__doc__ break return cls class Animal(object): def walk(self): 'Walk like a duck' class Dog(Animal): def walk(self): pass Dog = fix_docs(Dog) print Dog.walk.__doc__ 

在较新的Python版本中,最后一部分更简单美观:

 @fix_docs class Dog(Animal): def walk(self): pass 

这是一种与标准库中现有工具的devise完全匹配的Pythonic技术。 例如, functools.total_ordering类装饰器将缺less丰富的比较方法添加到类中。 又例如, functools.wraps修饰器将元数据从一个函数复制到另一个函数。

这是Paul McGuire的DocStringInheritor元类的变体。

  1. 如果子成员的文档string为空,则inheritance父成员的文档string。
  2. 如果子类docstring为空,它将inheritance父类docstring。
  3. 它可以inheritance任何基类的MRO中的任何类的docstring,就像常规的属性inheritance一样。
  4. 与类装饰器不同,元类是inheritance的,所以您只需要在顶级基类中设置元类一次,并且在整个OOP层次结构中都会发生文档stringinheritance。

 import unittest import sys class DocStringInheritor(type): """ A variation on http://groups.google.com/group/comp.lang.python/msg/26f7b4fcb4d66c95 by Paul McGuire """ def __new__(meta, name, bases, clsdict): if not('__doc__' in clsdict and clsdict['__doc__']): for mro_cls in (mro_cls for base in bases for mro_cls in base.mro()): doc=mro_cls.__doc__ if doc: clsdict['__doc__']=doc break for attr, attribute in clsdict.items(): if not attribute.__doc__: for mro_cls in (mro_cls for base in bases for mro_cls in base.mro() if hasattr(mro_cls, attr)): doc=getattr(getattr(mro_cls,attr),'__doc__') if doc: if isinstance(attribute, property): clsdict[attr] = property(attribute.fget, attribute.fset, attribute.fdel, doc) else: attribute.__doc__ = doc break return type.__new__(meta, name, bases, clsdict) class Test(unittest.TestCase): def test_null(self): class Foo(object): def frobnicate(self): pass class Bar(Foo, metaclass=DocStringInheritor): pass self.assertEqual(Bar.__doc__, object.__doc__) self.assertEqual(Bar().__doc__, object.__doc__) self.assertEqual(Bar.frobnicate.__doc__, None) def test_inherit_from_parent(self): class Foo(object): 'Foo' def frobnicate(self): 'Frobnicate this gonk.' class Bar(Foo, metaclass=DocStringInheritor): pass self.assertEqual(Foo.__doc__, 'Foo') self.assertEqual(Foo().__doc__, 'Foo') self.assertEqual(Bar.__doc__, 'Foo') self.assertEqual(Bar().__doc__, 'Foo') self.assertEqual(Bar.frobnicate.__doc__, 'Frobnicate this gonk.') def test_inherit_from_mro(self): class Foo(object): 'Foo' def frobnicate(self): 'Frobnicate this gonk.' class Bar(Foo): pass class Baz(Bar, metaclass=DocStringInheritor): pass self.assertEqual(Baz.__doc__, 'Foo') self.assertEqual(Baz().__doc__, 'Foo') self.assertEqual(Baz.frobnicate.__doc__, 'Frobnicate this gonk.') def test_inherit_metaclass_(self): class Foo(object): 'Foo' def frobnicate(self): 'Frobnicate this gonk.' class Bar(Foo, metaclass=DocStringInheritor): pass class Baz(Bar): pass self.assertEqual(Baz.__doc__, 'Foo') self.assertEqual(Baz().__doc__, 'Foo') self.assertEqual(Baz.frobnicate.__doc__, 'Frobnicate this gonk.') def test_property(self): class Foo(object): @property def frobnicate(self): 'Frobnicate this gonk.' class Bar(Foo, metaclass=DocStringInheritor): @property def frobnicate(self): pass self.assertEqual(Bar.frobnicate.__doc__, 'Frobnicate this gonk.') if __name__ == '__main__': sys.argv.insert(1, '--verbose') unittest.main(argv=sys.argv) 

仅供参考,仅供参考:从Python 3.5开始, inspect.getdoc自动从inheritance层次中检索文档string。

上面的反馈对于Python 2来说是非常有用的,或者如果你想在合并父母和孩子的文档方面更有创意。

我也为docstringinheritance创build了一些轻量级的工具 。 这些支持一些不错的默认文档string样式(numpy,google,reST)。 您也可以轻松使用自己的文档string样式

以下改编也可以处理属性和混合类。 我也遇到了一个情况,我不得不使用func.__func__ (“instancemethod”),但是我不完全确定为什么其他解决scheme不能解决这个问题。

 def inherit_docs(cls): for name in dir(cls): func = getattr(cls, name) if func.__doc__: continue for parent in cls.mro()[1:]: if not hasattr(parent, name): continue doc = getattr(parent, name).__doc__ if not doc: continue try: # __doc__'s of properties are read-only. # The work-around below wraps the property into a new property. if isinstance(func, property): # We don't want to introduce new properties, therefore check # if cls owns it or search where it's coming from. # With that approach (using dir(cls) instead of var(cls)) # we also handle the mix-in class case. wrapped = property(func.fget, func.fset, func.fdel, doc) clss = filter(lambda c: name in vars(c).keys() and not getattr(c, name).__doc__, cls.mro()) setattr(clss[0], name, wrapped) else: try: func = func.__func__ # for instancemethod's except: pass func.__doc__ = doc except: # some __doc__'s are not writable pass break return cls 
 def fix_docs(cls): """ copies docstrings of derived attributes (methods, properties, attrs) from parent classes.""" public_undocumented_members = {name: func for name, func in vars(cls).items() if not name.startswith('_') and not func.__doc__} for name, func in public_undocumented_members.iteritems(): for parent in cls.mro()[1:]: parfunc = getattr(parent, name, None) if parfunc and getattr(parfunc, '__doc__', None): if isinstance(func, property): # copy property, since its doc attribute is read-only new_prop = property(fget=func.fget, fset=func.fset, fdel=func.fdel, doc=parfunc.__doc__) cls.func = new_prop else: func.__doc__ = parfunc.__doc__ break return cls