Python3方法在不破坏装饰符或违反DRY的情况下继承docstring

2024-04-25 21:48:48 发布

您现在位置:Python中文网/ 问答频道 /正文

这个问题似乎经常出现在StackOverflow和其他地方,但我没有找到一个完全满意的解决方案。在

似乎有两种常见的解决方案。第一个(例如http://article.gmane.org/gmane.comp.python.general/630549)使用函数修饰符:

class SuperClass:
    def my_method(self):
        '''Has a docstring'''
        pass

class MyClass(SuperClass):
    @copy_docstring_from(SuperClass)
    def my_method(self):
        pass

assert SuperClass.my_method.__doc__ == MyClass.my_method._doc__

这可能是最直接的方法,但它至少需要重复父类名一次,如果在直接祖先中找不到docstring,也会变得更加复杂。在

第二种方法使用元类或类修饰符(参见Inheriting methods' docstrings in PythonInherit a parent class docstring as __doc__ attributehttp://mail.python.org/pipermail/python-list/2011-June/606043.html),如下所示:

^{pr2}$

但是,使用这种方法,docstring只在类创建之后设置,因此装饰器无法访问,因此以下操作将不起作用:

def log_docstring(fn):
    print('docstring for %s is %s' % (fn.__name__, fn.__doc__)
    return fn

class MyClass(SuperClass, metaclass=MagicHappeningHere):
# or
#@frobnicate_docstrings
#class MyClass2(SuperClass): 
    @log_docstring
    def method(self):
        pass

第三个有趣的想法已经在Inherit docstrings in Python class inheritance中讨论过。这里,函数修饰符实际上包装了方法并将其转换为方法描述符,而不仅仅是更新其docstring。然而,这看起来像是在用大锤敲开一个螺母,因为它将方法转换为一个方法描述符(这可能也有性能影响,尽管我没有检查),并且也不会使docstring对任何其他装饰器可用(在上面的示例中,实际上会使它们崩溃,因为方法描述符没有__name__属性)。在

是否有一个解决方案可以避免上述所有缺点,即不需要我重复自己的操作,并立即使用decorator分配docstring?在

我对Python3的解决方案感兴趣。在


Tags: 方法selfdocmydefmyclasspass解决方案
3条回答

请改用类修饰符:

@inherit_docstrings
class MyClass(SuperClass):
    def method(self):
        pass

其中inherit_docstrings()定义为:

^{pr2}$

演示:

>>> class SuperClass:
...     def method(self):
...         '''Has a docstring'''
...         pass
... 
>>> @inherit_docstrings
... class MyClass(SuperClass):
...     def method(self):
...         pass
... 
>>> MyClass.method.__doc__
'Has a docstring'

这将在定义整个类之后设置docstring,而不必首先创建实例。在

如果您需要方法装饰器可以使用的docstring,那么不幸的是,您只能使用复制父类的decorator。在

这样做的原因是,在定义类主体时,不能反思超类将是什么。类定义期间的本地命名空间无权访问传递给类工厂的参数。在

您可以使用元类将基类添加到本地名称空间,然后使用修饰符再次拉出基类,但在我看来,这会变得丑陋、快速:

import sys

class InheritDocstringMeta(type):
    _key = '__InheritDocstringMeta_bases'

    def __prepare__(name, bases, **kw):
        return {InheritDocstringMeta._key: bases}

    def __call__(self, name, bases, namespace, **kw):
        namespace.pop(self._key, None)

def inherit_docstring(func):
    bases = sys._getframe(1).f_locals.get(InheritDocstringMeta._key, ())
    for base in bases:
        for parent in base.mro():
            if hasattr(parent, func.__name__):
                func.__doc__ = getattr(parent, func.__name__).__doc__
    return func

演示用法:

>>> class MyClass(SuperClass, metaclass=InheritDocstringMeta):
...     @inherit_docstring
...     def method(self):
...         pass
... 
>>> MyClass.method.__doc__
'Has a docstring'

我认为可以通过注入一个知道类层次结构的decorator来使用元类__prepare__方法:

def log_docstring(fn):
    print('docstring for %r is %r' % (fn, fn.__doc__))
    return fn

class InheritableDocstrings(type):
    def __prepare__(name, bases):
        classdict = dict()

        # Construct temporary dummy class to figure out MRO
        mro = type('K', bases, {}).__mro__[1:]
        assert mro[-1] == object
        mro = mro[:-1]

        def inherit_docstring(fn):
            if fn.__doc__ is not None:
                raise RuntimeError('Function already has docstring')

            # Search for docstring in superclass
            for cls in mro:
                super_fn = getattr(cls, fn.__name__, None)
                if super_fn is None:
                    continue
                fn.__doc__ = super_fn.__doc__
                break
            else:
                raise RuntimeError("Can't inherit docstring for %s: method does not "
                                   "exist in superclass" % fn.__name__)

            return fn

        classdict['inherit_docstring'] = inherit_docstring
        return classdict

class Animal():
    def move_to(self, dest):
        '''Move to *dest*'''
        pass

class Bird(Animal, metaclass=InheritableDocstrings):
    @log_docstring
    @inherit_docstring
    def move_to(self, dest):
        self._fly_to(dest)

assert Animal.move_to.__doc__ == Bird.move_to.__doc__

印刷品:

^{pr2}$

当然,这种方法还有一些其他问题: -一些分析工具(例如pyflakes)会抱怨使用(显然)未定义的inherit_docstring名称 -如果父类已经有一个不同的元类(例如ABCMeta),那么它就不起作用了。在

从python3.5开始,^{}在继承树中搜索docstring。因此,如果您将子对象的docstring留空,它将从父对象检索它。这样就避免了代码重复的需要,像sphinx这样的自动代码生成器将做正确的事情。在

$ cat mwe.py
import inspect

class A:
    def foo(self):
        """Fool!"""
        return 42

class B(A):
    def foo(self):
        return super().foo()

print(A.foo.__doc__, B.foo.__doc__, A().foo.__doc__, B().foo.__doc__,
      inspect.getdoc(A.foo), inspect.getdoc(B.foo),
      inspect.getdoc(A().foo), inspect.getdoc(B().foo))
$ python mwe.py
Fool! None Fool! None Fool! Fool! Fool! Fool!

相关问题 更多 >