web-dev-qa-db-ja.com

Pythonでは、メソッドをオーバーライドしていることをどのように示すことができますか?

Javaでは、たとえば、@Override注釈は、オーバーライドのコンパイル時チェックを提供するだけでなく、優れた自己文書化コードを作成します。

私はただドキュメントを探しています(それがpylintのようなチェッカーへのインジケータである場合、それはボーナスです)。どこかにコメントやdocstringを追加できますが、Pythonでオーバーライドを示す慣用的な方法は何ですか?

142
Bluu

UPDATE(23.05.2015):これとfwc:sの回答に基づいて、pipインストール可能パッケージを作成しました https://github.com/mkorpela/overrides

時々、私はこの質問を見てここに行きます。これは主に、コードベースで同じバグを(再び)見た後に起こります:「インターフェース」のメソッドの名前を変更する際に、クラスを実装する「インターフェース」を忘れている人がいます。

まあPython ai n't Java but Pythonは力がある-そして明示的は暗黙的よりも優れている-そしてこのことが私を助けてくれる現実の現実の具体的なケース。

オーバーライドデコレータのスケッチは次のとおりです。これにより、パラメーターとして指定されたクラスが、装飾されているメソッドと同じメソッド(または何か)の名前を持っていることを確認します。

より良い解決策が考えられる場合は、ここに投稿してください!

def overrides(interface_class):
    def overrider(method):
        assert(method.__in dir(interface_class))
        return method
    return overrider

次のように機能します。

class MySuperInterface(object):
    def my_method(self):
        print 'hello world!'


class ConcreteImplementer(MySuperInterface):
    @overrides(MySuperInterface)
    def my_method(self):
        print 'hello kitty!'

誤ったバージョンを実行すると、クラスのロード中にアサーションエラーが発生します。

class ConcreteFaultyImplementer(MySuperInterface):
    @overrides(MySuperInterface)
    def your_method(self):
        print 'bye bye!'

>> AssertionError!!!!!!!
179
mkorpela

次に、interface_class名の指定を必要としない実装を示します。

import inspect
import re

def overrides(method):
    # actually can't do this because a method is really just a function while inside a class def'n  
    #assert(inspect.ismethod(method))

    stack = inspect.stack()
    base_classes = re.search(r'class.+\((.+)\)\s*\:', stack[2][4][0]).group(1)

    # handle multiple inheritance
    base_classes = [s.strip() for s in base_classes.split(',')]
    if not base_classes:
        raise ValueError('overrides decorator: unable to determine base class') 

    # stack[0]=overrides, stack[1]=inside class def'n, stack[2]=outside class def'n
    derived_class_locals = stack[2][0].f_locals

    # replace each class name in base_classes with the actual class type
    for i, base_class in enumerate(base_classes):

        if '.' not in base_class:
            base_classes[i] = derived_class_locals[base_class]

        else:
            components = base_class.split('.')

            # obj is either a module or a class
            obj = derived_class_locals[components[0]]

            for c in components[1:]:
                assert(inspect.ismodule(obj) or inspect.isclass(obj))
                obj = getattr(obj, c)

            base_classes[i] = obj


    assert( any( hasattr(cls, method.__name__) for cls in base_classes ) )
    return method
27
fwc

これをドキュメント化のみを目的とする場合、独自のオーバーライドデコレータを定義できます。

def override(f):
    return f


class MyClass (BaseClass):

    @override
    def method(self):
        pass

実際にオーバーライドをチェックするような方法でoverride(f)を作成しない限り、これは本当に見た目だけです。

しかし、これはPythonです。なぜJavaのように書くのでしょうか。

12
Ber

PythonはJavaではありません。もちろん、コンパイル時のチェックなどはありません。

Docstringのコメントはたくさんあると思います。これにより、メソッドのすべてのユーザーがhelp(obj.method)を入力して、メソッドがオーバーライドであることを確認できます。

class Foo(Interface)を使用してインターフェイスを明示的に拡張することもできます。これにより、ユーザーはhelp(Interface.method)を入力して、メソッドが提供する機能についてのアイデアを得ることができます。

6
Triptych

@mkorpelaでの即興演奏 偉大な答え 、これは

より正確なチェック、命名、発生したエラーオブジェクト

_def overrides(interface_class):
    """
    Function override annotation.
    Corollary to @abc.abstractmethod where the override is not of an
    abstractmethod.
    Modified from answer https://stackoverflow.com/a/8313042/471376
    """
    def confirm_override(method):
        if method.__not in dir(interface_class):
            raise NotImplementedError('function "%s" is an @override but that'
                                      ' function is not implemented in base'
                                      ' class %s'
                                      % (method.__name__,
                                         interface_class)
                                      )

        def func():
            pass

        attr = getattr(interface_class, method.__name__)
        if type(attr) is not type(func):
            raise NotImplementedError('function "%s" is an @override'
                                      ' but that is implemented as type %s'
                                      ' in base class %s, expected implemented'
                                      ' type %s'
                                      % (method.__name__,
                                         type(attr),
                                         interface_class,
                                         type(func))
                                      )
        return method
    return confirm_override
_


実際には次のようになります。

NotImplementedError "基本クラスには実装されていません"

_class A(object):
    # ERROR: `a` is not a implemented!
    pass

class B(A):
    @overrides(A)
    def a(self):
        pass
_

より説明的なNotImplementedErrorエラーが発生します

_function "a" is an @override but that function is not implemented in base class <class '__main__.A'>
_

フルスタック

_Traceback (most recent call last):
  …
  File "C:/Users/user1/project.py", line 135, in <module>
    class B(A):
  File "C:/Users/user1/project.py", line 136, in B
    @overrides(A)
  File "C:/Users/user1/project.py", line 110, in confirm_override
    interface_class)
NotImplementedError: function "a" is an @override but that function is not implemented in base class <class '__main__.A'>
_


NotImplementedError "期待される実装タイプ"

_class A(object):
    # ERROR: `a` is not a function!
    a = ''

class B(A):
    @overrides(A)
    def a(self):
        pass
_

より説明的なNotImplementedErrorエラーが発生します

_function "a" is an @override but that is implemented as type <class 'str'> in base class <class '__main__.A'>, expected implemented type <class 'function'>
_

フルスタック

_Traceback (most recent call last):
  …
  File "C:/Users/user1/project.py", line 135, in <module>
    class B(A):
  File "C:/Users/user1/project.py", line 136, in B
    @overrides(A)
  File "C:/Users/user1/project.py", line 125, in confirm_override
    type(func))
NotImplementedError: function "a" is an @override but that is implemented as type <class 'str'> in base class <class '__main__.A'>, expected implemented type <class 'function'>
_




@mkorpelaの答えの素晴らしいところは、初期化フェーズ中にチェックが行われることです。チェックは「実行」する必要はありません。前の例を参照すると、_class B_は初期化されることはありません(B())が、NotImplementedErrorは引き続き発生します。これは、overridesエラーがより早くキャッ​​チされることを意味します。

2

他の人が言ったようにJava @Overideタグはありませんが、上記ではデコレータを使用して独自に作成できますが、内部辞書を使用する代わりにgetattrib()グローバルメソッドを使用することをお勧めします次のようなもの:

def Override(superClass):
    def method(func)
        getattr(superClass,method.__name__)
    return method

独自にgetattr()をキャッチしたい場合は、独自のエラーを発生させてcatchを試すことができますが、この場合はgetattrメソッドの方が良いと思います。

また、これはクラスメソッドや変数を含むクラスにバインドされたすべてのアイテムをキャッチします

2
Bicker x 2

@mkorpelaの素晴らしい答えに基づいて、私は同様のパッケージを書いた(ipromisepypigithub )さらに多くのチェックを行います:

AがBとCから継承すると仮定します。BがCから継承すると仮定します。ipromiseは、

  • A.fがB.fをオーバーライドする場合、B.fが存在し、AがBから継承する必要があります(これはオーバーライドパッケージからのチェックです)。

  • A.fがB.fをオーバーライドすることを宣言するパターンはありません。次に、C.fをオーバーライドすることを宣言します。

  • A.fがC.fをオーバーライドすることを宣言するパターンはありませんが、B.fはオーバーライドを宣言しません。

  • パターンA.fはC.fをオーバーライドすることを宣言しませんが、B.fはD.fからオーバーライドすることを宣言します。

また、抽象メソッドの実装をマークおよびチェックするためのさまざまな機能があります。

1
Neil G

聴覚は最も簡単で、JythonでJavaクラス:

class MyClass(SomeJavaClass):
     def __init__(self):
         setattr(self, "name_of_method_to_override", __method_override__)

     def __method_override__(self, some_args):
         some_thing_to_do()
0
user3034016