继承方法';Python中的文档字符串

继承方法';Python中的文档字符串,python,oop,inheritance,docstring,template-method-pattern,Python,Oop,Inheritance,Docstring,Template Method Pattern,我有一个包含docstring的OO层次结构,它需要和代码本身一样多的维护。例如: class Swallow(object): def airspeed(self): """Returns the airspeed (unladen)""" raise NotImplementedError class AfricanSwallow(Swallow): def airspeed(self): # whatever 现在的问题是

我有一个包含docstring的OO层次结构,它需要和代码本身一样多的维护。例如:

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

class AfricanSwallow(Swallow):
    def airspeed(self):
        # whatever
现在的问题是,
airspeed
不继承超类方法的docstring。我知道我可以使用模板方法模式保存文档字符串,即

class Swallow(object):
    def airspeed(self):
        """Returns the airspeed (unladen)"""
        return self._ask_arthur()
并在每个子类中实现
\u ask\u arthur
。然而,我想知道是否有另一种方法可以继承docstring,也许是我还没有发现的某个装饰器?

这是一种变体

  • 如果子成员的 文档字符串为空
  • 如果子类docstring为,则它继承父类docstring 空的
  • 它可以从中的任何类继承docstring 任何基类的MRO,就像常规属性继承一样
  • 与类装饰器不同,元类是继承的,因此您只需要在某个顶级基类中设置一次元类,而docstring继承将在整个OOP层次结构中发生


  • 以类装饰器样式编写一个函数来为您进行复制。在Python2.5中,可以在创建类后直接应用它。在以后的版本中,可以使用符号进行应用

    以下是如何做到这一点的第一个切入点:

    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
    

    这是一种Python技术,与标准库中现有工具的设计完全匹配。例如,类装饰器向类添加缺少的富比较方法。另一个例子是,装饰器将元数据从一个函数复制到另一个函数。

    下面的自适应还处理属性和mixin类。我还遇到了一种情况,我必须使用
    func.\uu func\uu
    (用于“instancemethod”),但我不完全确定其他解决方案为什么没有遇到这个问题

    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
    

    F.Y.I对于刚刚在这个主题上结结巴巴的人来说:从Python3.5开始,自动从继承层次结构中检索docstring

    因此,上面的响应对于Python2非常有用,或者如果您希望在合并父对象和子对象的docstring时更具创造性的话


    我也创造了一些。它们支持一些很好的默认文档字符串样式(numpy、google、reST)。您也可以轻松地使用自己的docstring样式

    仅此示例就值得+1(您在官方文档之外看到的Python引用太少了)。幸运的是,剩下的问题也证明了投票的合理性;)看一看——有很多解决方案。应该可以编写一个类修饰符,遍历所有的方法,看看它们的
    \uuuuuuuuuuuu
    是否为
    None
    ,如果是,则借用super
    \uuuuuuuuuuuuu
    。现在没有时间尝试。。。另请参见@wberry:class decorators不是一个选项,因为我的目标是Python2.5。对不起,我应该早点说。@NeilG:我更新了代码,使之与Python3兼容。(现在)唯一需要的更改是用
    类Baz(Bar,metaclass=docStringInheritator)
    定义
    Baz
    ,而不是在类体中定义
    \uuuuuuu metaclass=docStringInheritator
    。如果有一个没有缺点的更简单的解决方案,这就太过了。如果派生类具有属性(Python 2.7.9),就会崩溃。请看:@RedX:谢谢你让我注意到这一点。由于属性
    \uuuuu doc\uuuuuu
    属性不能通过重新分配来修改,因此我修改了上面的代码,通过将class属性重新分配给一个新属性(使用旧的getter、setter、deleter,但使用一个新的docstring),来处理属性。我希望将由此派生的代码合并到MIT许可库中。您是否愿意在MIT兼容许可下重新授权您的代码,以便我可以合法地这样做?此答案包含一个错误
    vars(cls)
    包含类中的一对
    “文档”:None
    ,它在
    if func.\uu文档
    中引发一个
    AttributeError
    。应跳过该项或将其置于特殊情况下。也是将继承属性描述的版本。
    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