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
関数が作成されます。 最後に、オブジェクト ob
が print_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
関数は値 10
を Grand 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
には、name
、age
、およびデフォルトで False
に設定されている bool 変数 ugly
の 3つのデータ メンバーがあります。 3つのメンバー メソッドは、name
、age
、および id を出力します。
今度は、装飾された Parent
から派生した Child
クラス内に、新しいデータ メンバー school
が導入されます。 これにより、クラスは変数 ugly
の属性を False
から True
に変更します。
Parent
用の jack
と Child
用の 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
をデフォルトとして、Parent
は name
、age
、および ugly
で始まり、Child
はそのリストの最後に school
を追加します (ugly
はすでに リスト)。
この結果、リストには name
、age
、ugly
、および 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
が最後に保持されます。
デフォルトのないフィールドを持つクラス ParentBase
と ChildBase
は、デフォルトを持つフィールドを持つ ParentDefaultsBase
と ChildDefaultsBase
の前に来るため、データ クラスのルールも満たされます。
その結果、Child
は依然として Parent
のサブクラスですが、Parent
クラスと Child
クラスは正しいフィールド順になっています。
# __ Program Above __
print(signature(Parent))
print(signature(Child))
出力:
まとめ
この記事では、Python でのデータ クラスの継承について詳しく説明します。 データ クラス、子クラス、複数レベルの継承、基本クラスからサブクラスへの属性の混合などの概念が徹底的に説明されています。