这个问题似乎在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 Python,Inherit a parent class docstring as __doc__ attribute,http://mail.python.org/pipermail/python-list/2011-June/606043.html),如下所示:
class MyClass1(SuperClass, metaclass=MagicHappeningHere):
def method(self):
pass
# or
@frobnicate_docstrings
class MyClass2(SuperClass):
def method(self):
pass
assert SuperClass.my_method.__doc__ == MyClass1.my_method._doc__
assert SuperClass.my_method.__doc__ == MyClass2.my_method._doc__
但是,使用这种方法,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__
属性。)
是否有解决方案可以避免上述所有缺点,即不需要我重复自己并使用装饰器立即分配文档字符串?
我对Python 3的解决方案感兴趣。
答案 0 :(得分:2)
改为使用类装饰器:
@inherit_docstrings
class MyClass(SuperClass):
def method(self):
pass
其中inherit_docstrings()
定义为:
from inspect import getmembers, isfunction
def inherit_docstrings(cls):
for name, func in getmembers(cls, isfunction):
if func.__doc__: continue
for parent in cls.__mro__[1:]:
if hasattr(parent, name):
func.__doc__ = getattr(parent, name).__doc__
return cls
演示:
>>> 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 ,而不必先创建实例。
如果您需要方法装饰器可用的文档字符串,那么很遗憾,您完全陷入了与父类重复的装饰器。
这样做的原因是你无法在定义类体时内省超类的含义。类定义期间的本地命名空间无权访问传递给类工厂的参数。
你可以使用元类将基类添加到本地命名空间,然后使用装饰器再将它们拉出来,但在我看来,它变得丑陋,快速:
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'
答案 1 :(得分:1)
我认为可以通过注入一个知道类层次结构的装饰器来使用元类'__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__
打印:
docstring for <function Bird.move_to at 0x7f6286b9a200> is 'Move to *dest*'
当然,这种方法还有其他一些问题:
- 一些分析工具(例如pyflakes)会抱怨使用(显然)未定义的inherit_docstring
名称
- 如果父类已经有不同的元类(例如ABCMeta
),它就不起作用。
答案 2 :(得分:1)
从Python 3.5开始,inspect.getdoc
在继承树中搜索文档字符串。因此,如果您将子代的文档字符串留空,它将从父代检索它。这样就避免了重复代码的需要,像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!