8

この質問は、StackOverflow と他の場所の両方で定期的に出てくるようですが、完全に満足できる解決策をどこにも見つけることができませんでした。

一般的なソリューションには 2 つのタイプがあるようです。最初のもの (例えば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__

これはおそらく最も簡単な方法ですが、親クラス名を少なくとも 1 回繰り返す必要があり、直接の祖先に docstring が見つからない場合はさらに複雑になります。

2 番目のアプローチでは、メタクラスまたはクラス デコレータを使用します ( Pythonでメソッドのドキュメント文字列を継承する、親クラスのドキュメント文字列を __doc__ 属性として継承する、 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

3 つ目の興味深いアイデアは、Python クラスの継承でドキュメント文字列を継承する で説明されています。ここで、関数デコレータは、単にドキュメント文字列を更新するのではなく、実際にメソッドをラップしてメソッド記述子に変換します。ただし、これはメソッドをメソッド記述子に変換するため (私は確認していませんが、パフォーマンスに影響を与える可能性があります)、ドキュメント文字列を他のデコレータ (および上記の例では、メソッド記述子に属性がないため、実際にはクラッシュし__name__ます)。

上記のすべての欠点を回避する解決策はありますか?

Python 3 のソリューションに興味があります。

4

3 に答える 3

4

代わりにクラス デコレータを使用します。

@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 が設定されます。

メソッド デコレータで使用できる 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'
于 2013-06-30T17:59:54.490 に答える
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

于 2013-06-30T18:34:04.303 に答える