web-dev-qa-db-ja.com

Java抽象/インターフェース設計Python

私はすべてが同じメソッドを共有するいくつかのクラスを持っていますが、実装は異なります。 Javaでは、これらのクラスのそれぞれにインターフェースを実装させるか、抽象クラスを拡張することは理にかなっています。 Pythonはこれに似たものがありますか、それとも別のアプローチを取るべきですか?

38
Matt

Pythonのインターフェースの背後には少し話があります。長年に渡って揺るがなかった元の態度は、あなたがそれらを必要としないということです:Python EAFP(許可よりも許しを求める方が簡単)の原則に基づいて動作します。つまり、指定する代わりにIdno、ICloseableオブジェクトを受け入れる場合、必要なときにオブジェクトをcloseにしようとするだけです。例外が発生した場合は、例外が発生します。

したがって、この考え方では、クラスを個別に記述し、必要に応じて使用します。それらの1つが要件に準拠していない場合、プログラムは例外を発生させます。逆に、適切なメソッドを使用して別のクラスを作成すると、特定のインターフェースを実装することを指定しなくても、クラスは機能します。

これはかなりうまく機能しますが、特に大規模なソフトウェアプロジェクトでは、インターフェイスの明確な使用例があります。 Python=の最終決定は、 abc モジュールを提供することでした。これにより、抽象的な基本クラスを記述できますつまり、すべてのメソッドをオーバーライドしない限りインスタンス化できないクラスです。それらを使用する価値があるかどうかは、あなたの判断です。

PEP ABCの紹介 は、私が説明できるよりもはるかによく説明しています。

オブジェクト指向プログラミングの領域では、オブジェクトと対話するための使用パターンは、「呼び出し」と「検査」という2つの基本カテゴリに分類できます。

呼び出しとは、そのメソッドを呼び出すことによってオブジェクトと対話することを意味します。通常、これはポリモーフィズムと組み合わせられるため、特定のメソッドを呼び出すと、オブジェクトのタイプに応じて異なるコードが実行される可能性があります。

インスペクションとは、(オブジェクトのメソッド以外の)外部コードがそのオブジェクトのタイプまたはプロパティを調べ、その情報に基づいてそのオブジェクトをどのように処理するかを決定する機能を意味します。

どちらの使用パターンも、一般的な目的は同じです。つまり、多様で新規の可能性のあるオブジェクトの処理を統一的にサポートできると同時に、処理の決定を異なる種類のオブジェクトごとにカスタマイズできるようにします。

古典的なOOP理論では、呼び出しは推奨される使用パターンであり、検査は積極的に推奨されておらず、以前の手続き型プログラミングスタイルの遺物と見なされています。しかし、実際にはこのビューは単純すぎて独断的であり、柔軟性に欠け、Pythonのような言語の動的な性質と非常に相容れない一種の設計の剛性につながります。

特に、オブジェクトクラスの作成者が予期していなかった方法でオブジェクトを処理する必要があることがよくあります。そのオブジェクトのすべての可能なユーザーのニーズを満たすすべてのオブジェクトメソッドに組み込むことが、常に最良のソリューションであるとは限りません。さらに、従来のOOP動作がオブジェクト内に厳密にカプセル化されているという要件とは対照的な、強力なディスパッチの哲学が数多くあります。例としては、ルールやパターンマッチ駆動のロジックがあります。

一方、古典的なOOP理論家による検査の批判の1つは、形式化の欠如と検査対象のアドホックな性質です。Pythonなどの言語では、オブジェクトの任意の側面を反映し、外部コードから直接アクセスできる場合、オブジェクトが特定のプロトコルに準拠しているかどうかをテストするさまざまな方法があります。たとえば、「このオブジェクトは変更可能なシーケンスコンテナですか?」は「リスト」の基本クラスを探すか、「__ getitem__」という名前のメソッドを探すことができます。ただし、これらのテストは明白に見えるかもしれませんが、どちらかが正しくないことに注意してください。 。

一般的に合意された救済策は、テストを標準化し、それらを正式な取り決めにグループ化することです。これは、継承メカニズムまたはその他の手段を介して、各クラスに一連の標準のテスト可能なプロパティを関連付けることで最も簡単に実行できます。各テストには、一連のプロミスが含まれています。これには、クラスの一般的な動作に関するプロミスと、他に利用できるクラスメソッドに関するプロミスが含まれています。

このPEPは、Abstract Base Classes(ABC)と呼ばれるこれらのテストを編成するための特定の戦略を提案します。 ABCは、単純にPythonオブジェクトの継承ツリーに追加されるクラスであり、オブジェクトの特定の機能を外部インスペクターに通知します。テストはisinstance()を使用して行われます。特定のABCの存在は、テストに合格したこと。

さらに、ABCは、型の特徴的な動作を確立するメソッドの最小セットを定義します。 ABCタイプに基づいてオブジェクトを識別するコードは、それらのメソッドが常に存在することを信頼できます。これらの各メソッドには、ABCのドキュメントで説明されている一般化された抽象的なセマンティック定義が伴います。これらの標準の意味定義は強制されていませんが、強くお勧めします。

Pythonの他のすべてのものと同様に、これらの約束は紳士協定の性質にあります。つまり、この場合、言語はABCで行われた約束の一部を強制しますが、具体的なクラスの実装者が保証することになります。残りのものは保持されます。

63
Katriel

私はPythonにそれほど慣れていませんが、そうでないことを推測するのは危険です。

インターフェースがJavaに存在する理由は、それらがcontractを指定するためです。たとえば、_Java.util.List_を実装するものは、add()メソッドは、インターフェースで定義された一般的な動作に準拠します。Listの(正常な)実装を特定のクラスを知らなくてもドロップし、インターフェースで定義された一連のメソッドを呼び出して、同じ一般的な動作を取得できます。

さらに、開発者とコンパイラーの両方が、そのようなメソッドが存在し、その正確なクラスを知らなくても、問題のオブジェクトで呼び出し可能であることを知ることができます。これは、さまざまな実装クラスを許可するために静的型付けで必要なポリモーフィズムの形式ですが、それらがすべて正当であることを認識しています。

これは静的に型付けされていないため、Pythonでは実際には意味がありません。オブジェクトのクラスを宣言する必要も、オブジェクトに呼び出すメソッドが確実に存在することをコンパイラーに説得する必要もありません。ダックタイピングの世界における「インターフェース」は、メソッドを呼び出し、オブジェクトがそのメッセージを適切に処理できると信頼するのと同じくらい簡単です。

注-より知識の豊富なPythonistaからの編集を歓迎します。

5
Andrzej Doyle

このようなものを使用できるかもしれません。これは抽象クラスとして機能します。したがって、すべてのサブクラスはfunc1()を実装する必要があります

class Abstract:

    def func1(self):
        raise NotImplementedError("The method not implemented")
4
M S

3.5で library を記述し、Pythonでインターフェイスを作成できるようにしました。

要点は、inspectを利用してクラスデコレータを記述することです。

import inspect


def implements(interface_cls):
    def _decorator(cls):
        verify_methods(interface_cls, cls)
        verify_properties(interface_cls, cls)
        verify_attributes(interface_cls, cls)
        return cls

    return _decorator


def verify_methods(interface_cls, cls):
    methods_predicate = lambda m: inspect.isfunction(m) or inspect.ismethod(m)
    for name, method in inspect.getmembers(interface_cls, methods_predicate):
        signature = inspect.signature(method)
        cls_method = getattr(cls, name, None)
        cls_signature = inspect.signature(cls_method) if cls_method else None
        if cls_signature != signature:
            raise NotImplementedError(
                "'{}' must implement method '{}({})' defined in interface '{}'"
                .format(cls.__name__, name, signature, interface_cls.__name__)
            )


def verify_properties(interface_cls, cls):
    prop_attrs = dict(fget='getter', fset='setter', fdel='deleter')
    for name, prop in inspect.getmembers(interface_cls, inspect.isdatadescriptor):
        cls_prop = getattr(cls, name, None)
        for attr in prop_attrs:
            # instanceof doesn't work for class function comparison
            if type(getattr(prop, attr, None)) != type(getattr(cls_prop, attr, None)):
                raise NotImplementedError(
                    "'{}' must implement a {} for property '{}' defined in interface '{}'"  # flake8: noqa
                    .format(cls.__name__, prop_attrs[attr], name, interface_cls.__name__)
                )


def verify_attributes(interface_cls, cls):
    interface_attributes = get_attributes(interface_cls)
    cls_attributes = get_attributes(cls)
    for missing_attr in (interface_attributes - cls_attributes):
        raise NotImplementedError(
            "'{}' must have class attribute '{}' defined in interface '{}'"
            .format(cls.__name__, missing_attr, interface_cls.__name__)
        )


def get_attributes(cls):
    boring = dir(type('dummy', (object,), {}))
    return set(item[0] for item in inspect.getmembers(cls)
               if item[0] not in boring and not callable(item[1]))

その後、次のようなクラスを作成できます。

class Quackable:
    def quack(self) -> bool:
        pass


@implements(Quackable)
class MallardDuck:    
    def quack(self) -> bool:
        pass

ただし、以下ではエラーが発生します。

@implements(Quackable)
class RubberDuck:    
    def quack(self) -> str:
        pass

NotImplementedError: 'RubberdDuck' must implement method 'quack((self) -> bool)' defined in interface 'Quackable'
2
Kamil Sindi