Python でのデータ クラスの継承

Jay Shaw 2023年10月10日
  1. Python での継承
  2. Python でのマルチレベル継承
  3. Python でデータ クラスの継承を使用して、基本クラスとサブクラスの間で既定の属性と既定以外の属性を混在させる
  4. まとめ
Python でのデータ クラスの継承

バージョン 3.7 以降では、Python でデータ クラス継承が導入されました。 この記事では、マルチレベルの継承と、Python でデータ クラスの継承を使用する方法について幅広く説明します。

Python での継承

Python のデータ クラス継承は、親クラスからサブクラスのデータを取得するために使用されます。これにより、コードの繰り返しが減り、コードが再利用可能になります。

継承の例を見てみましょう。

このプログラムは dataclass ライブラリ パッケージをインポートして、装飾されたクラスを作成できるようにします。 ここで作成される最初のクラスは Parent で、これには文字列 name と整数 age の 2つのメンバー メソッドがあります。

次に、Parent のサブクラスがここに作成されます。 Child クラスは、新しいメンバー メソッド school を導入します。

クラス Parent に対してインスタンスオブジェクト jack が作成され、クラスに 2つの引数が渡されます。 Child クラス用に別のインスタンス オブジェクト jack_son が作成されます。

Child クラスは Parent のサブクラスであるため、データ メンバーは Child クラスで派生できます。 これが Python のデータ クラス継承の主な特徴です。

from dataclasses import dataclass


@dataclass
class Parent:
    name: str
    age: int

    def print_name(self):
        print(f"Name is '{self.name}' and age is= {self.age}")


@dataclass
class Child(Parent):
    school: str


jack = Parent("Jack snr", 35)
jack_son = Child("Jack jnr", 12, school="havard")

jack_son.print_name()

出力:

C:\python38\python.exe "C:/Users/Win 10/PycharmProjects/class inheritance/2.py"
Name is 'Jack jnr' and age is= 12

Process finished with exit code 0

Python でのマルチレベル継承

Python でのデータ クラスの継承がどのように機能するかを見てきたので、次にマルチレベル継承の概念を見ていきます。 親クラスから作成されたサブクラスが、後続の孫クラスの親として使用されるタイプの継承です。

次の例は、複数レベルの継承を単純な形式で示しています。

親クラス

クラス Parent はコンストラクタ __init__ とメンバー メソッド print_method で作成されます。 コンストラクターは、ステートメント "Initialized in Parent" を出力して、クラスがその子クラスから呼び出されたときに表示されるようにします。

print_method 関数にはパラメーター b があり、このメソッドが呼び出されたときに出力されます。

子クラス

Child クラスは Parent から派生し、そのコンストラクター内にステートメントを出力します。 super().__init__ は、子クラスを持つ基本クラスを参照します。

super() を使用して、子クラスで使用される潜在的な協調多重継承がメソッド解決順序 (MRO) で適切な次の親クラス関数を呼び出すようにします。

メンバ メソッド print_method がオーバーロードされ、print ステートメントが b の値を出力します。 ここでも、super() はその親クラスのメンバー メソッドを参照します。

孫クラス

この時点で、プログラムは、Child クラスから継承された別のクラスを作成するために構造を定型化 (繰り返しコード) するだけです。 print_method の内部では、b の値は super() を使用してインクリメントされます。

メイン機能

最後に、オブジェクト ob を作成し、GrandChild() のインスタンスにする main 関数が作成されます。 最後に、オブジェクト obprint_method を呼び出します。

これは、Python でデータ クラスの継承が使用されている場合に、マルチレベル クラスがどのようにスタックされるかです。

class Parent:
    def __init__(self):
        print("Initialized in Parent")

    def print_method(self, b):
        print("Printing from class Parent:", b)


class Child(Parent):
    def __init__(self):
        print("Initialized in Child")
        super().__init__()

    def print_method(self, b):
        print("Printing from class Child:", b)
        super().print_method(b + 1)


class GrandChild(Child):
    def __init__(self):
        print("Initialized in Grand Child")
        super().__init__()

    def print_method(self, b):
        print("Printing from class Grand Child:", b)
        super().print_method(b + 1)


if __name__ == "__main__":
    ob = GrandChild()
    ob.print_method(10)

出力:

C:\python38\python.exe "C:/Users/Win 10/PycharmProjects/class inheritance/3.py"
Initialized in Grand Child
Initialized in Child
Initialized in Parent
Printing from class Grand Child: 10
Printing from class Child: 11
Printing from class Parent: 12

Process finished with exit code 0

ここでコードが何をするかを理解しましょう。

main 関数は値 10Grand Child クラスの print_method 関数に渡します。 MRO (メソッド解決順序) に従って、プログラムはまずクラス Grand Child を実行し、__init__ ステートメントを出力してから、その親である Child クラスに移動します。

Child クラスと Parent クラスは、MRO に従って __init__ ステートメントを出力し、コンパイラは GrandChild クラスの print_method に戻ります。 このメソッドは、10 (b の値) を出力し、super() を使用して、そのスーパークラスである Child クラスの b の値をインクリメントします。

次に、コンパイラは Child クラスの print_method に移動し、11 を出力します。 次に、MRO の最後のレベルには、12 を出力する Parent クラスがあります。

Parent クラスにスーパークラスが存在しないため、プログラムは終了します。

Python のデータ クラス継承でマルチレベル継承がどのように機能するかを理解したので、次のセクションでは、親クラスから属性を継承する概念とその変更方法について説明します。

Python でデータ クラスの継承を使用して、基本クラスとサブクラスの間で既定の属性と既定以外の属性を混在させる

Python のデータ クラス継承で、子クラスを使用してその親クラスのデータ メンバーにアクセスする方法と、マルチレベル継承がどのように機能するかを見てきました。 ここで、子クラスがそのスーパークラスのデータ メンバーにアクセスできるかどうか、それを変更できるかどうかという疑問が生じます。

答えはイエスですが、TypeError を回避する必要があります。 たとえば、以下のプログラムには、Parent クラスとサブクラス Child の 2つのクラスがあります。

クラス Parent には、nameage、およびデフォルトで False に設定されている bool 変数 ugly の 3つのデータ メンバーがあります。 3つのメンバー メソッドは、nameage、および id を出力します。

今度は、装飾された Parent から派生した Child クラス内に、新しいデータ メンバー school が導入されます。 これにより、クラスは変数 ugly の属性を False から True に変更します。

Parent 用の jackChild 用の jack_son の 2つのオブジェクトが作成されます。 これらのオブジェクトはクラスに引数を渡し、両方のオブジェクトが print_id メソッドを呼び出して詳細を出力します。

このメソッドを使用して基本クラスのデフォルト値を変更する際の主な問題は、TypeError が発生することです。

from dataclasses import dataclass


@dataclass
class Parent:
    name: str
    age: int
    ugly: bool = False

    def print_name(self):
        print(self.name)

    def print_age(self):
        print(self.age)

    def print_id(self):
        print(f"ID: Name - {self.name}, age = {self.age}")


@dataclass
class Child(Parent):
    school: str
    ugly: bool = True


jack = Parent("jack snr", 32, ugly=True)
jack_son = Child("Mathew", 14, school="cambridge", ugly=True)

jack.print_id()
jack_son.print_id()

出力:

    raise TypeError(f'non-default argument {f.name!r} '
TypeError: non-default argument 'school' follows default argument

このエラーの背後にある理由は、Python のデータ クラスの継承では、データ クラスが属性を混合する方法が原因で、属性を基本クラスのデフォルトで使用できず、サブクラスでデフォルト (位置属性) なしで使用できないことです。

これは、属性が MRO の下部の最初からマージされ、最初に表示された順序で属性の順序付きリストを構築し、オーバーライドが元の位置に残るためです。

ugly をデフォルトとして、Parentnameage、および ugly で始まり、Child はそのリストの最後に school を追加します (ugly はすでに リスト)。

この結果、リストには nameageugly、および school が含まれます。school にはデフォルトがないため、__init__ 関数は結果を誤ったパラメーターとしてリストします。

@dataclass デコレーターが新しいデータ クラスを作成するとき、クラスのすべての基本クラスをリバース MRO (オブジェクトから開始) で検索し、各基本クラスからのフィールドを各データ クラスのフィールドの順序付きマッピングに追加します。 見つけます。

次に、すべての基本クラス フィールドが追加された後で、そのフィールドを順序付きマッピングに追加します。 この組み合わされたフィールドの計算された順序付きマッピングは、作成されたすべてのメソッドで使用されます。

フィールドの配置により、派生クラスは基本クラスに取って代わります。

デフォルト値のないフィールドがデフォルト値のあるフィールドの後に続く場合、TypeError が生成されます。 これは、単一のクラスで発生する場合でも、クラスの継承によって発生する場合でも当てはまります。

この問題を回避するための最初の代替手段は、デフォルト値を持つフィールドを、異なる基本クラスを使用して MRO 順序の後の位置に強制することです。 Parent のように、基本クラスとして使用されるクラスに直接フィールドを設定することは絶対に避けてください。

このプログラムには、フィールドを持つ基本クラスがあり、デフォルトのないフィールドは分離されています。 パブリック クラスは、base-with および base-without クラスから派生します。

public クラスのサブクラスは、基本クラスを前もって置きます。

from dataclasses import dataclass


@dataclass
class _ParentBase:
    name: str
    age: int


@dataclass
class _ParentDefaultsBase:
    ugly: bool = False


@dataclass
class _ChildBase(_ParentBase):
    school: str


@dataclass
class _ChildDefaultsBase(_ParentDefaultsBase):
    ugly: bool = True


@dataclass
class Parent(_ParentDefaultsBase, _ParentBase):
    def print_name(self):
        print(self.name)

    def print_age(self):
        print(self.age)

    def print_id(self):
        print(f"ID: Name - {self.name}, age = {self.age}")


@dataclass
class Child(_ChildDefaultsBase, Parent, _ChildBase):
    pass


Amit = Parent("Amit snr", 32, ugly=True)

Amit_son = Child("Amit jnr", 12, school="iit", ugly=True)

Amit.print_id()
Amit_son.print_id()

出力:

C:\python38\python.exe "C:/main.py"
The Name is Amit snr and Amit snr is 32 year old
The Name is Amit jnr and Amit jnr is 12 year old

Process finished with exit code 0

ここで作成された MRO は、フィールドをデフォルトなしデフォルトありの基本クラスに分割し、継承順序を慎重に選択することで、デフォルトのあるフィールドよりもデフォルトのないフィールドを優先します。 Child の MRO は次のとおりです。

<class 'object'>
        ||
<class '__main__._ParentBase'>,
        ||
<class '__main__._ChildBase'>
        ||
<class '__main__._ParentDefaultsBase'>,
        ||
<class '__main__.Parent'>,
        ||
<class '__main__._ChildDefaultsBase'>,
        ||
<class '__main__._Child'>

Parent は新しいフィールドを作成しませんが、ParentDefaultsBase からフィールドを継承し、フィールドのリスト順で最後に来るべきではありません。 そのため、正しい注文タイプを満たすために ChildDefaultsBase が最後に保持されます。

デフォルトのないフィールドを持つクラス ParentBaseChildBase は、デフォルトを持つフィールドを持つ ParentDefaultsBaseChildDefaultsBase の前に来るため、データ クラスのルールも満たされます。

その結果、Child は依然として Parent のサブクラスですが、Parent クラスと Child クラスは正しいフィールド順になっています。

# __ Program Above __

print(signature(Parent))
print(signature(Child))

出力:

署名関数

まとめ

この記事では、Python でのデータ クラスの継承について詳しく説明します。 データ クラス、子クラス、複数レベルの継承、基本クラスからサブクラスへの属性の混合などの概念が徹底的に説明されています。

関連記事 - Python Class

関連記事 - Python Dataclass