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

发布于 2024-12-15 05:26:39 字数 601 浏览 3 评论 0原文

我有一个带有文档字符串的面向对象层次结构,需要与代码本身一样多的维护。例如,

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

class AfricanSwallow(Swallow):
    def airspeed(self):
        # whatever

现在的问题是 AfricanSwallow.airspeed 没有继承超类方法的文档字符串。我知道我可以使用模板方法模式保留文档字符串,即

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

在每个子类中实现 _ask_arthur 。然而,我想知道是否还有另一种方法可以继承文档字符串,也许是一些我还没有发现的装饰器?

I have an OO hierarchy with docstrings that take as much maintenance as the code itself. E.g.,

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

class AfricanSwallow(Swallow):
    def airspeed(self):
        # whatever

Now, the problem is that AfricanSwallow.airspeed does not inherit the superclass method's docstring. I know I can keep the docstring using the template method pattern, i.e.

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

and implementing _ask_arthur in each subclass. However, I was wondering whether there's another way to have docstrings be inherited, perhaps some decorator that I hadn't discovered yet?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

很酷不放纵 2024-12-22 05:26:39

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

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

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)

This is a variation on Paul McGuire's DocStringInheritor metaclass.

  1. It inherits a parent member's docstring if the child member's
    docstring is empty.
  2. It inherits a parent class docstring if the child class docstring is
    empty.
  3. It can inherit the docstring from any class in
    any of the base classes's MROs, just like regular attribute inheritance.
  4. Unlike with a class decorator, the metaclass is inherited, so you only need to set the metaclass once in some top-level base class, and docstring inheritance will occur throughout your OOP hierarchy.

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)
宛菡 2024-12-22 05:26:39

以类装饰器风格编写一个函数来为您进行复制。在Python2.5中,创建类后直接应用即可。在更高版本中,您可以使用 @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

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

Write a function in a class-decorator style to do the copying for you. In Python2.5, you can apply it directly after the class is created. In later versions, you can apply with the @decorator notation.

Here's a first cut at how to do it:

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__

In newer Python versions, the last part is even more simple and beautiful:

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

This is a Pythonic technique that exactly matches the design of existing tools in the standard library. For example, the functools.total_ordering class decorator add missing rich comparison methods to classes. And for another example, the functools.wraps decorator copies metadata from one function to another.

ら栖息 2024-12-22 05:26:39

供那些刚刚在这个主题上绊倒的人参考:从 Python 3.5 开始,检查。 getdoc 自动从继承层次结构中检索文档字符串。

因此,上面的响应对于 Python 2 很有用,或者如果您想更有创意地合并父级和子级的文档字符串。

我还创建了一些用于文档字符串继承的轻量级工具。它们支持一些开箱即用的默认文档字符串样式(numpy、google、reST)。您也可以轻松使用自己的文档字符串样式

F.Y.I for people just now stumbling on this topic: As of Python 3.5, inspect.getdoc automatically retrieves docstrings from the inheritance hierarchy.

The responses above are thus useful for Python 2, or if you want to be more creative with merging the docstrings of parents and children.

I've also created some lightweight tools for docstring inheritance. These support some nice default docstring styles (numpy, google, reST) out of the box. You can easily use your own docstring style as well

不离久伴 2024-12-22 05:26:39

以下改编还处理属性和 mixin 类。我还遇到过一种情况,我必须使用 func.__func__ (对于“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

The following adaptation also handles properties and mixin classes. I also came across a situation where I had to use func.__func__ (for "instancemethod"s), but I'm not completely sure why the other solutions didn't encouter that problem.

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
喵星人汪星人 2024-12-22 05:26:39
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
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
并安 2024-12-22 05:26:39

这是一个非常古老的线程。但是如果有人正在寻找一种简单的方法,您可以使用 __init_subclass__ 来完成此操作,只要您继承该类,并且您有权访问父类进行更改,就会调用该方法。

def __init_subclass__(cls, **kwargs):
    super().__init_subclass__(**kwargs)
    parent_method_docstr = {}
    for i, v in ParentClass.__dict__.items():
        if v and callable(v) and v.__doc__ is not None:
            parent_method_docstr[i] = v.__doc__

    for i, v in cls.__dict__.items():
        if v and callable(v) and v.__doc__ is None and i in parent_method_docstr:
            v.__doc__ = parent_method_docstr[i]

It is a very old thread. But If anyone is looking for a simple way, you can do this with __init_subclass__ which is called whenever you inherit that class, if you have access to parent class to make a change.

def __init_subclass__(cls, **kwargs):
    super().__init_subclass__(**kwargs)
    parent_method_docstr = {}
    for i, v in ParentClass.__dict__.items():
        if v and callable(v) and v.__doc__ is not None:
            parent_method_docstr[i] = v.__doc__

    for i, v in cls.__dict__.items():
        if v and callable(v) and v.__doc__ is None and i in parent_method_docstr:
            v.__doc__ = parent_method_docstr[i]
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文