ホームページ  >  記事  >  バックエンド開発  >  Python クラスで複数のコンストラクター メソッドのオーバーロードとジェネリック メソッドを定義する方法

Python クラスで複数のコンストラクター メソッドのオーバーロードとジェネリック メソッドを定義する方法

WBOY
WBOY転載
2023-05-09 14:34:231293ブラウズ

    「汎用メソッド」とは何ですか?

    異なる型に対して同じ操作を実装する複数のメソッドで構成されるメソッド。

    たとえば、

    次の方法でカスタム日付クラス (CustomDate) を作成する必要があるという要件があります:

    • タイムスタンプ

    • 年、月、日 (3 つの整数を含むタプル)

    • ISO 形式の文字列

    • Datetime クラス

    一般実装

    from datetime import date, datetime
    class CustomDate:
        def __init__(self, arg):
            if isinstance(arg, (int, float)):
                self.__date = date.fromtimestamp(arg)
            elif isinstance(arg, tuple) and len(arg) == 3 and all(map(lambda x: isinstance(x, int), arg):
                self.__date = date(*arg)
            elif isinstance(arg, str):
                self.__date = date.fromisoformat(arg)
            elif isinstance(arg, datetime):
                self.__date = datetime.date()
            else:
                raise TypeError("could not create instance from " + type(arg).__name__)
        @property
        def date():
            return self.__date

    注: 受信についてはここでは説明しません。 /time stamp が正当であるかどうかは、大まかにタイプを判断するだけです。

    もっと良い方法はありますか?

    さまざまなビルド メソッドを複数のメソッドに分割し、functoolssingledispatchmethod デコレータを使用して、メソッドに渡されたパラメータのタイプに基づいてどれを呼び出すかを決定できます。

    from datetime import date, datetime
    from functools import singledispatchmethod
    class CustomDate:
        @singledispatchmethod
        def __init__(self, arg):
            raise TypeError("could not create instance from " + type(arg).__name__)
        @__init__.register(int)
        @__init__.register(float)
        def __from_timestamp(self, arg):
            self.__date = date.fromtimestamp(arg)
        @__init__.register(tuple)
        def __from_tuple(self, arg):
            if len(arg) == 3 and all(map(lambda x: isinstance(x, int), arg)):
                self.__date = date(*arg)
            else:
                raise ValueError("could not create instance from a malformed tuple")
        @__init__.register(str)
        def __from_isoformat(self, arg):
            self.__date = date.fromisoformat(arg)
        @__init__.register(datetime)
        def __from_datetime(self, arg):
            self.__date = arg.date()
        @property
        def date(self):
            return self.__date

    このようにして、各パラメーター タイプの初期化を個別のメソッドに分離できます。

    欠点

    #1 単一ディスパッチ

    呼び出し中にどのメソッド実装を使用するかは、ディスパッチ アルゴリズムによって決まります。アルゴリズムが単一のパラメーターの型のみに基づいて、どのメソッド実装を使用するかを決定する場合、それは単一ディスパッチと呼ばれます。

    singledispatchmethod シングルディスパッチです。つまり、最初のパラメータのみが考慮されます。実際のビジネスではこれでは十分ではありません。

    #2 は入力をサポートしていません

    ただし、上記のように、要素のタイプを決定するには if/else を使用する必要があります。タプル。つまり、typing.Tuple[int, int, int] は使用できません。

    妥協案として、ThreeIntTuple クラスを定義して制限し、これらの判断を CustomDate クラスから分離することもできます。

    ここでは参考のためにアイデアを提供するだけです。実装はしません (もっと良い方法があるため xD)。

    代替案: マルチメソッド ライブラリ

    このライブラリは標準ライブラリの 1 つではないため、pip を通じてインストールする必要があります:

    pip install multimethod

    利点

    multimethod マルチディスパッチ アルゴリズムを使用しており、より複雑なシナリオに適切に対応できます。さらに、このライブラリでは、typing の型も適切にサポートされています。

    より良い練習方法

    上の質問に戻ると、次のように改善できます:

    • multimethod メソッドを使用します。 singledispatchmethod;

    • 使用 Tuple[int, int, int] を使用して tuple を置き換えます (不要になりました)タプルの長さと要素の型を手動で確認します。

    from datetime import date, datetime
    from typing import Tuple, Union
    from multimethod import multimethod
    class CustomDate:
        @multimethod
        def __init__(self, arg):
            raise TypeError("could not create instance from " + type(arg).__name__)
        @__init__.register
        def __from_timestamp(self, arg: Union[int, float]):
            self.__date = date.fromtimestamp(arg)
        @__init__.register
        def __from_tuple(self, arg: Tuple[int, int, int]):
            self.__date = date(*arg)
        @__init__.register
        def __from_isoformat(self, arg: str):
            self.__date = date.fromisoformat(arg)
        @__init__.register
        def __from_datetime(self, arg: datetime):
            self.__date = arg.date()
        @property
        def date(self):
            return self.__date

    究極の実践的な方法 (実際のメソッドのオーバーロード)

    これを行う前に、まず質問してください。 (これは次の内容と密接に関係しています):

    class A:
        def a(self):
            print(1)
        def a(self):
            print(2)
    A().a()

    上記のコードは何を出力しますか?それともエラーがスローされますか?

    出力 2

    Python では、重複する名前のメソッドが定義されている場合、最後のメソッドが前のメソッドを上書きします。

    しかし、メタクラスを通じてこの動作を変更できることはご存知ないかもしれません:

    class MetaA(type):
        class __prepare__(dict):
            def __init__(*args):
                pass
            def __setitem__(self, key, value):
                if self.get('a'):  # Line 7
                    super().__setitem__('b', value)  # Line 8
                else:	
                    super().__setitem__(key, value)
    class A(metaclass=MetaA):
        def a(self):
            print(1)
        def a(self):
            print(2)
    A().a()  # => 1
    A().b()  # => 2  # Line 22

    7 行目と 8 行目では、a## を同じ名前で # メソッドの名前が変更されます b が実行され、22 行目で正常に呼び出されます。

    multimethod の管理者はこれをうまく利用し、重複した名前を持つメソッドを処理して「特別な効果」を実現しました。

    トピックに戻り、次の改善を行うことができます。

    • multimethod.multidataCustomDate クラスに設定します。メタクラス;

    • すべてのメソッドに名前を付けます

      __init__

    • from datetime import date, datetime
      from typing import Tuple, Union
      from multimethod import multimeta
      class CustomDate(metaclass=multimeta):
          def __init__(self, arg: Union[int, float]):
              self.__date = date.fromtimestamp(arg)
          def __init__(self, arg: Tuple[int, int, int]):
              self.__date = date(*arg)
          def __init__(self, arg: str):
              self.__date = date.fromisoformat(arg)
          def __init__(self, arg: datetime):
              self.__date = arg.date()
          def __init__(self, arg):
              raise TypeError("could not create instance from " + type(arg).__name__)
          @property
          def date(self):
              return self.__date
    効果の点では、これは静的言語のメソッドのオーバーロードとまったく同じです。

    以上がPython クラスで複数のコンストラクター メソッドのオーバーロードとジェネリック メソッドを定義する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    声明:
    この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。