Pythonプログラミング:クラスとオブジェクト、クロージャ、デコレータ、型注釈、MRO

Pythonプログラミング:クラスとオブジェクト、クロージャ、デコレータ、型注釈、MRO

  1. クラスとオブジェクト

Pythonではclassキーワードを使用してクラスを定義し、オブジェクト = クラス名()の形式でオブジェクトを作成します。

  • オブジェクトは変数と同様に型を宣言する必要はありません。
  • オブジェクトの作成は「クラス関数」を呼び出すような書き方をします。

1.1 メンバーメソッド

  • クラス内で定義されるメンバーメソッドは、必ずselfというパラメータを持ち、すべてのパラメータの最初に配置する必要があります。
  • selfはクラスオブジェクト自身を表し、メンバーメソッド内でメンバ変数にアクセスするにはselfを使用する必要があります。
  • クラスオブジェクトでメンバーメソッドを呼び出す際は、selfパラメータは存在しないものとして扱うことができます。
  • クラスオブジェクトでメンバーメソッドを呼び出すと、selfはPythonによって自動的に渡されます。
# クラスの定義
class Student:
    # メンバ変数
    name = None

    # メンバーメソッド、selfパラメータは必須
    def introduce(self, location):
        # selfを使ってメンバ変数にアクセス
        print(f"こんにちは、私は{self.name}です。{location}に住んでいます。")
        
# オブジェクトの作成
s1 = Student()
# メンバ変数の値を設定
s1.name = 'Taro'
# メンバーメソッドの呼び出し(selfパラメータは存在しないものとして扱う)
s1.introduce('東京') # こんにちは、私はTaroです。東京に住んでいます。

1.2 コンストラクタ

  • コンストラクタはクラスオブジェクトを作成する際に自動的に呼び出されます。
  • Pythonではコンストラクタの名前は__init__で固定されています(前後にアンダースコアが2つずつ)。
  • 自分でコンストラクタを定義しない場合、デフォルトで空の引数のコンストラクタが存在します。自分でコンストラクタを定義すると、デフォルトの空の引数のコンストラクタは無効になります。
  • コンストラクタもメンバーメソッドの一種であり、selfパラメータが必要です。
  • コンストラクタはオブジェクトを作成する際にメンバ変数に値を割り当てるためによく使用されます。
class Learner:

    # コンストラクタ
    def __init__(self, name, age, hometown):
        self.name = name
        self.age = age
        self.hometown = hometown

    def display_info(self):
        print(self.name, self.age, self.hometown)

# オブジェクト作成時にコンストラクタが自動呼び出され、selfは存在しないものとして扱う
student1 = Learner('Jiro', 25, '大阪')
student1.display_info() # Jiro 25 大阪

1.3 クラス属性とクラスメソッドと静的メソッド

クラス属性:

  • オブジェクトはクラスから作成されますが、その属性とメソッドは各オブジェクト固有のもので、共有されません。
  • クラスにも属性とメソッドがあり、それらはクラスから作成されたすべてのオブジェクトと共有できます。さらに、クラス自体もオブジェクトを作成せずにアクセスできます。
  • 以下のコードではクラス属性を定義していますが、オブジェクト属性ではありません:
  • クラス属性にアクセスするには、オブジェクトを表すselfではなく、クラス名を使用します。
class Employee:
    # これらはすべてクラス属性
    name = None
    position = None
    employee_count = 0
    
    def __init__(self, name, position):
        # クラス属性にアクセスするにはクラス名を使用します
        Employee.employee_count += 1
    
print(Employee.employee_count) # 0
emp1 = Employee('Sato', 'Developer')
print(emp1.employee_count) # 1
emp2 = Employee('Tanaka', 'Designer')
print(emp2.employee_count) # 2
print(Employee.employee_count) # 2
# すべてのオブジェクトとクラス自体がクラス属性employee_countを共有していることがわかります

インスタンス属性:

  • __initコンストラクタ内でselfを使用して定義され、各オブジェクト固有のものです。
  • 以下のコードではインスタンス属性を定義していますが、クラス属性ではありません:
  • インスタンス属性にアクセスするには、クラス名ではなくselfを使用します。
class Person:
    def __init__(self, name, age, occupation):
        self.name = name
        self.age = age
        self.occupation = occupation

クラスメソッド:

  • クラスメソッドはオブジェクトを作成せずに、クラス名でアクセスできます。オブジェクトからもアクセスできます。
  • @classmethodデコレータを使用して定義し、最初のパラメータはclsです(selfではありません)。clsはオブジェクト自体ではなくクラス自体を表します。クラスメソッド内でクラス内の他のクラスメソッドやクラス属性にアクセスするにはclsを使用する必要があります。
class DataProcessor:
    # クラス属性
    version = "1.0"
    
    @classmethod
    def get_version(cls):
        # クラスメソッドはクラス属性にclsでアクセス
        return cls.version
    
# クラスメソッドの呼び出し
print(DataProcessor.get_version()) # 1.0

# インスタンスからもクラスメソッドを呼び出せますが、効果は同じです
processor = DataProcessor()
print(processor.get_version()) # 1.0

インスタンスメソッド:

  • クラス内で定義され、最初のパラメータはselfで、インスタンス属性にアクセスできます。

静的メソッド:

  • 静的メソッドは@staticmethodデコレータを使用してクラス内で定義されます。
  • 静的メソッドは最初のパラメータとしてclsselfを使用しません。
  • 静的メソッドはクラスとオブジェクトの両方からアクセスできます。
  • 静的メソッドはクラスに関連する機能を実装するために使用されますが、実装過程でクラスやオブジェクトのメンバーにアクセスまたは変更する必要はありません(clsselfパラメータがないとアクセスできません)。通常は、クラスに関連するいくつかの論理演算を行うために使用されます。
class MathUtils:
    @staticmethod
    def add_numbers(num1, num2):
        return num1 + num2
    
    # 静的メソッドの呼び出し
    result = MathUtils.add_numbers(5, 10)
    print(result)  # 出力: 15
    
    # インスタンスからも静的メソッドを呼び出せますが、効果は同じです
    calculator = MathUtils()
    result = calculator.add_numbers(5, 10)
    print(result)  # 出力: 15

1.4 マジックメソッド

Pythonに組み込まれているオブジェクトメソッド(最初のパラメータはself)をマジックメソッドと呼びます:

__str__

  • オブジェクトを出力する際に表示される内容を制御し、役に立たないメモリアドレスを出力しないようにします。
  • このメソッドは文字列を返す必要があり、その文字列がオブジェクトを印刷した際に出力される内容です。
# __str__マジックメソッドを定義しない場合
class Person1:
     def __init__(self, name, age):
         self.name = name
         self.age = age

p1 = Person1('Yamada', 30)
print(p1) # <__main__.Person object at 0x0000016A8BDE5810>

# __str__マジックメソッドを使用
class Person2:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f'{self.name} ({self.age}歳)'

p2 = Person2('Yamada', 30)
# マジックメソッドが返す内容を出力
print(p2) # Yamada (30歳)

__lt__

  • オブジェクトを直接比較しようとするとエラーが発生します。このメソッドはオブジェクト間の比較をサポートするために使用されます。
  • このメソッドの2番目のパラメータotherは比較に参加する別のオブジェクトを指し、selfパラメータはオブジェクト自体を指します。
  • このメソッドの戻り値はTrueまたはFalseです。
class Product1:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def __lt__(self, other):
        return self.price < other.price

item1 = Product1('ノート', 100)
item2 = Product1('ペン', 200)
# item2がメソッドのotherパラメータに対応
print(item1 < item2) # True

その他のマジックメソッド:

  • __le__:オブジェクト間の比較で<=>=演算子をサポートするために使用されます。
  • 使用方法は__lt__と全く同じです。
  • __eq__:オブジェクト間の比較で==演算子をサポートするために使用されます。
  • 使用方法は__lt__と全く同じです。

1.5 プライベートメンバー

  • プライベートメンバーはクラスオブジェクトからアクセスして使用することはできません。
  • プライベートメンバーはクラス内の他のメンバーからアクセスして使用することができます。
  • プライベートメンバーを定義するには、メソッドや変数名の前に__(2つのアンダースコア)を追加するだけです。
class Worker:
    # メンバ変数にもデフォルト値を設定できます
    __company = 'Tech Corp'

    def __init__(self, name, position):
        self.name = name
        self.position = position

    def get_company(self):
        # プライベートメンバーにアクセス
        return self.__company

w1 = Worker('Suzuki', 'Engineer')
print(w1.__company) # オブジェクトがプライベートメンバーにアクセスするとエラーになります
print(w1.get_company()) # Tech Corp

1.6 継承

1.6.1 単一継承と多重継承

  • 継承とは、子クラスが親クラスからメンバ変数とメソッド(プライベートなものを除く)を継承することを意味します。その結果、子クラスは継承されたメンバーを持ち、直接使用できます(子クラスでも同じメンバーが定義されているかのように)。
  • 書き方:class クラス名(親クラス名)
  • 複数の親クラスを継承できます。複数の親クラス名は括弧内でカンマで区切ります。
  • 継承された複数の親クラスに同名のメンバ変数やメソッドがある場合、括弧内の左から右への順序で優先度が決まります。

単一継承:

# 親クラスの定義
class Device:
    model = None
    manufacturer = None

    def power_on(self):
        print('親クラスDeviceのメンバーメソッドが呼び出されました')

# 子クラスが親クラスを継承
class Smartphone(Device):
    color = 'black'

phone1 = Smartphone()
print(phone1.color) # black
# 子クラスオブジェクトは継承された親クラスメソッドを直接呼び出せます
phone1.power_on() # 親クラスDeviceのメンバーメソッドが呼び出されました

多重継承:

class Device:
    model = None
    manufacturer = None

    def power_on(self):
        print('親クラスDeviceのメンバーメソッドが呼び出されました')

class CameraModule:
    resolution = '4K'
    
    def take_photo(self):
        print('写真を撮りました')
        
class iPhone(Device, CameraModule):
    # 継承された2つの親クラスのすべてのメンバーを持ちます
    pass

1.6.2 オーバーライド

  • 子クラスは親クラスと同名の変数やメソッドをオーバーライドできます。オーバーライド後、子クラスから呼び出すとオーバーライドされたものが親クラスのものではなく呼び出されます。
  • オーバーライドの方法は、親クラスと同名の新しいメソッドや変数を直接定義することです。
class Device:
    model = None
    manufacturer = None
    name = "デバイス"

    def power_on(self):
        print('親クラスDeviceのメンバーメソッドが呼び出されました')

# 子クラスが親クラスを継承
class Smartphone(Device):
    color = 'black'
    def __init__(self, name):
        # 継承された属性を変更
        self.name = name

    # オーバーライドを実行
    def power_on(self):
        print('子クラスSmartphoneのメンバーメソッドが呼び出されました')

phone1 = Smartphone("iPhone")
phone1.power_on() # オーバーライドされたメソッドが呼び出され、親クラスのものではありません
print(phone1.name) # iPhone

1.6.3 親クラスの同名属性の呼び出し

  • 子クラスで親クラスの属性を呼び出すには2つの方法があります(親クラスメソッドの呼び出しは以下を参照):
  • 親クラス名.属性名
  • super().属性名
class Device:
    model = None
    manufacturer = '親クラスのメーカー'

    def power_on(self):
        print('親クラスDeviceのメンバーメソッドが呼び出されました')

class Smartphone(Device):
    color = 'black'
    manufacturer = '子クラスでオーバーライドしたメーカー'

    # オーバーライドを実行
    def power_on(self):
        # 親クラスメンバーの呼び出し
        print(super().manufacturer)

phone1 = Smartphone()
phone1.power_on() # 親クラスのメーカー

1.6.4 ポリモーフィズムと抽象化

  • 抽象化とは、親クラスがどのようなメソッドとメンバーがあるかを決定しますが、何も実行しません(pass)。具体的な実装は子クラスのオーバーライドによって決まります。
  • ポリモーフィズムとは、親クラスを受け取るパラメータですが、実際には子クラスオブジェクトが渡されることです。つまり、親クラスが抽象化し、子クラスが実際に働きます。
# 抽象クラスで抽象メソッドを定義
class Animal:
    def make_sound(self):
        pass

# 子クラスの定義
class Cat(Animal):
    def make_sound(self):
        print("ニャー")

# 親クラスオブジェクトを受け取るパラメータ
def let_animal_speak(animal: Animal):
    animal.make_sound()

# 実際には子クラスオブジェクトが渡される
let_animal_speak(Cat()) # ニャー

1.7 カスタムプロパティ

  • パラメータを必要とせず、値を返すだけのメンバーメソッドについては、@propertyデコレータを使用して「プロパティ」に変換できます。これにより、これらのメソッドは関数を呼び出すのではなく、プロパティとして使用できます。
  • 定義後、関数名がプロパティ名になります。
class Student:

    base_score = 85

    @property
    def final_score(self):
        return self.base_score * 1.2
    
student1 = Student()
# age関数をプロパティとして使用
print(student1.final_score) # 102.0
  1. 型注釈

2.1 基本的な使い方

  • コードの実際の実行には何の影響もありませんが、サードパーティのIDEツールで型のヒントとして使用されます。自動補完や型チェックなどに役立ちます。
  • 対象の範囲:
  • 変数、オブジェクトの型注釈。
  • 関数の引数と戻り値の型注釈。
  • 書き方:
  • 変数、オブジェクトの注釈は、元のコードに直接、変数またはオブジェクトの後に: 型を追加するだけです。他の代入などのコードは変更しません。
  • オブジェクトの型注釈は、所属するクラスです。
  • メソッドの戻り値の注釈:def 関数名() -> 戻り値の型

2.2 コンテナ型の注釈

  • タプル型の型を詳細に注釈するには、各要素をマークする必要があります。
  • 辞書型の型を詳細に注釈するには、2つの型が必要です。最初はキー、2番目は値です。

2.3 typeによる型注釈の定義

  • 変数: 型という構文で注釈を行うだけでなく、コメント内でも型注釈を行うことができます。
  • 構文:# type: 型

2.4 Union結合型

  • さまざまな型を格納できるコンテナの場合、すべての要素の型を順番に書くのは面倒です。結合型を使用して、すべての可能性のある型を指定できます。
  • 使用方法:まずtypingからインポートし、Union[すべての可能性のある型をカンマで区切る]を使用します:
from typing import Union

# dataはintまたはstrのいずれか
def process_data(data: Union[int, str]) -> None:
    if isinstance(data, int):
        print(f"整数を処理中: {data}")
    elif isinstance(data, str):
        print(f"文字列を処理中: {data}")

process_data(42)
process_data("こんにちは")
  • isinstanceはオブジェクトとクラスの関係を判断し、TrueまたはFalseを返します:
  • 最初のパラメータにオブジェクトを、2番目のパラメータにクラスを記述します。
  1. クロージャ
  • クロージャとは、関数のネスト(入れ子)を指し、内部関数が外側関数のパラメータを使用し、外側関数の戻り値が内部関数自体であることです。
  • 変数が特定の関数でのみ使用される場合、クロージャを使用して安全性を高めることができます。
def outer(message: str):
    def inner(suffix: str):
        # 内部関数は外側関数のパラメータを直接使用できます
        print(f'{suffix}、私は"{message}"と伝えました')
    # 外側関数は内部関数自体を直接返します
    return inner

# この時点でouter_funcはinner関数と同等で、関数として使用します
outer_func = outer('こんにちは')
outer_func('山田') # 山田、私は"こんにちは"と伝えました
  • クロージャを使用しない場合、内部関数の外側でnum1変数を定義すると、インポート後に他のモジュールによってnum1変数が改ざんされるリスクがあります。クロージャを使用することでこのリスクを回避し、安全性を高めます。
  • 内部関数でnonlocalキーワードを使用して外側関数のパラメータを修飾することで、内部関数がこのパラメータの値を変更できるようになります。
def outer(value: int):
    def increment(amount: int):
        # nonlocalキーワードを使用して外側関数のパラメータを修飾し、変更できるようにします
        nonlocal value
        value += amount
        return value
    return increment

fn = outer(10)
print(fn(20)) # 30
print(fn(20)) # 50
  • 外側関数の変数は内部関数で共有されています。つまり、外側関数のパラメータが一度変更されると、内部関数を何回呼び出しても全体に有効になり、リセットされません。
  • クロージャの長所と短所:
  • 長所:
  • グローバル変数を定義せずに、内部関数を通じて継続的に外側関数のパラメータにアクセスし、変更できます。
  • コードの安全性を高めます。
  • 短所:
  • 内部関数が外側関数の値を継続的に参照するため、その部分のメモリスペースが解放されず、メモリを占有します(ただし、いくつかの変数が解放されないメモリは無視できます)。
  1. デコレータ
  • デコレータもクロージャの一種であり、既存の関数の機能を変更せずに、関数に新しい機能を追加する役割を果たします。
  • デコレータの本質は、関数を引数として受け取り、内部関数を返す関数です。つまり、前述のクロージャモジュールの外側関数の引数が変数ではなく、関数になるということです。

例:sleep関数の呼び出し前後にいくつかのコードを実行したい場合:

# 外側関数の引数は関数を受け取ります
def logging_decorator(func):
    def wrapper():
        print('関数を呼び出す前の処理')
        func()
        print('関数を呼び出した後の処理')
    return wrapper

def sleep():
    print('私は眠ります')

wrapped_func = logging_decorator(sleep)
wrapped_func()
"""
関数を呼び出す前の処理
私は眠ります
関数を呼び出した後の処理
"""

コードをデコレータの構文糖で書くことができます:

def logging_decorator(func):
    def wrapper():
        print('関数を呼び出す前の処理')
        func()
        print('関数を呼び出した後の処理')
    return wrapper

# @+外側関数名の方式を使用
@logging_decorator
def sleep():
    print('私は眠ります')

# sleep関数を直接呼び出すだけで上記の機能を実現します
sleep()
"""
関数を呼び出す前の処理
私は眠ります
関数を呼び出した後の処理
"""
  • この例では、カスタムの@logging_decoratorがデコレータであり、sleep()がデコレータで修飾される関数です。sleep()を呼び出すことは、実際には外側関数logging_decorator()の内部メソッドwrapper()を呼び出すことであり、外側メソッドの引数は修飾されるsleep()関数です。
  • sleep()関数は修飾されるため、sleep()関数を呼び出すことは修飾後の内容であり、関数本来の内容ではありません。

具体的な書き方のまとめ:

  1. どの関数を修飾するかを決定し、その関数にデコレータを追加します。
  2. デコレータと同名の外側関数を定義し、1つの引数(実際には修飾される関数です)を受け取ります。
  3. 内部関数(実際に実行される関数)を定義し、内部関数で外側関数の引数(修飾される関数)を使用します。
  4. 修飾される関数を直接呼び出し、実行結果は修飾後のコード(内部関数)になります。

注意点: 外側関数は修飾される関数の前に定義する必要があります。なぜなら、Pythonコードは上から下へ実行されるため、そうしないとPythonが現在行以降のコンテンツを認識できないからです。

  1. コンストラクタのオーバーライド補足

注意点:

  • Pythonでは、子クラスが明示的にコンストラクタ(__init__メソッド)を定義していない場合、子クラスのインスタンス化時にPythonは親クラスのコンストラクタを自動的に呼び出します。これはPythonのデフォルトの動作であり、親クラスのコンストラクタが提供する機能を使用するためです。
  • 子クラスでさらに初期化ロジックを追加する必要がある場合は、子クラス独自の__init__メソッドを定義し、その中で親クラスの__init__メソッドを必ず呼び出して、親クラスの初期化ロジックも実行されるようにする必要があります。
  • 親クラスの属性を呼び出す(2つの方法に差異はありません):
  • 親クラス名.属性名
  • super().属性名
  • 親クラスのメソッドを呼び出す(コンストラクタか通常のメソッドかに関わらず、以下の2つのルールを遵守する必要があります):
  • 親クラス名.メソッド名selfを最初のパラメータとして明示的に渡す必要があります。
  • super().メソッド名selfを最初のパラメータとして明示的に渡す必要はありません。なぜならsuper()がこのパラメータを自動的に処理するからです。多重継承の場合により有用であるため、今後はこの方法を使用することをお勧めします。

5.1 子クラスの単一継承でコンストラクタを定義しない場合

class BaseClass:
    def __init__(self):
        print("親クラスBaseClassのコンストラクタが実行されました!")

class DerivedClass(BaseClass):
    # 親クラスのコンストラクタを明示的に呼び出していません
    pass

# Derivedクラスのインスタンスを作成
derived = DerivedClass()

出力:

親クラスBaseClassのコンストラクタが実行されました!

5.2 子クラスの単一継承でコンストラクタをオーバーライドする場合

super()を使用して呼び出す:

class BaseClass:
    def __init__(self):
        print("親クラスBaseClassのコンストラクタが実行されました!")

    def regular_method(self):
        print("親クラスの通常メソッドが呼び出されました")

class DerivedClass(BaseClass):
    def __init__(self):
        super().__init__()
        super().regular_method()
        print("子クラスのコンストラクタが実行されました!")

# Derivedクラスのインスタンスを作成
derived = DerivedClass()

出力:

親クラスBaseClassのコンストラクタが実行されました!
親クラスの通常メソッドが呼び出されました
子クラスのコンストラクタが実行されました!

親クラス名を使用して呼び出す:

class BaseClass:
    def __init__(self):
        print("親クラスBaseClassのコンストラクタが実行されました!")

    def regular_method(self):
        print("親クラスの通常メソッドが呼び出されました")

class DerivedClass(BaseClass):
    def __init__(self):
        BaseClass.__init__(self)
        BaseClass.regular_method(self)
        print("子クラスのコンストラクタが実行されました!")

# Derivedクラスのインスタンスを作成
derived = DerivedClass()

5.3 MRO(メソッド解決順序)

  • Pythonでは、super()関数を使用して親クラスのコンストラクタを呼び出すと、メソッド解決順序(Method Resolution Order、MRO)に従います。MROはPythonがメソッドの継承順序を決定する方法であり、C3線形化アルゴリズムに基づいています。
  • super()の動作方法:コンストラクタでsuper().__init__()を使用する場合、Pythonは現在のクラスのMROを調べて次のクラスを見つけ、そのコンストラクタを呼び出します。
  • クラス名.__mro__を使用すると、クラスのMRO順序を取得できます。

クラスのMROをどう決定するのでしょうか?例えば、このコード:

class BaseClass1:
    def __init__(self):
        print("親クラス1のコンストラクタが実行されました!")

    def regular_method(self):
        print("親クラス1の通常メソッドが呼び出されました")

class BaseClass2:
    def __init__(self):
        print("親クラス2のコンストラクタが実行されました!")

    def regular_method(self):
        print("親クラス2の通常メソッドが呼び出されました")

class DerivedClass(BaseClass1, BaseClass2):
    def __init__(self):
        super().__init__()

print(DerivedClass.__mro__)
# 出力:(<class '__main__.DerivedClass'>, <class '__main__.BaseClass1'>, <class '__main__.BaseClass2'>, <class 'object'>)

1. まず大まかな方向を決定します

  • 現在の基底クラスは必ず最初の位置に配置されます。次に、現在のクラスの親クラスを考慮し、親クラスの参照順序に従って親クラスを左から右に並べます。
  • ある基底クラスに複数の親クラスがある場合、その親クラスのMROは子クラスと同じアルゴリズム(C3線形化アルゴリズム)で計算されます。
  • すべてのPythonクラスは最終的にobjectクラスを継承しているため、objectはMROの最後の要素です。
  • 最後に、Pythonは現在のクラス、親クラスのMRO、およびobjectを結合して、現在のクラスのMROを形成します。

現在のMROの順序:DerivedClass、BaseClass1のMRO、BaseClass2のMRO、object

2. C3線形化アルゴリズムに基づいて、2つの親クラスのMROを決定する詳細な手順:

  • 各親クラスについて、そのMROをリストアップします(この2つの親クラスは他のクラスを継承していません):
  • BaseClass1のMROは[BaseClass1, object]です。
  • BaseClass2のMROは[BaseClass2, object]です。
  • 2つの親クラスリストの最初の要素から始め、その要素が他のリストでも最初の要素であるか、または他のリストに存在しない場合、それが最終的なMROに追加されます。次に、その要素を含むすべてのリストからその要素を削除します。(重複した要素を削除していると考えることができます)
  • 上記の手順を繰り返し、すべてのリストが空になるまで続けます。

したがって、最終的に以下の順序が得られます:

  1. BaseClass1を選択します(最初のリストにあり、他のリストにはありません)。最終的なMROは[DerivedClass, BaseClass1]になります。最初のリストのobjectは条件を満たさないため処理しません。
  2. 次に2番目のリストのBaseClass2を選択します(他のリストにはありません)。最終的なMROは[DerivedClass, BaseClass1, BaseClass2]になります。
  3. 最後にobjectを選択し、最終的なMROを取得します:[DerivedClass, BaseClass1, BaseClass2, object]

親クラスにも継承されたクラスがある場合:

class BaseA:
    def __init__(self):
        print("BaseAの初期化")

class BaseB:
    def __init__(self):
        print("BaseBの初期化")

class BaseClass1(BaseA):
    def __init__(self):
        super().__init__()
        print("BaseClass1の初期化")

class BaseClass2(BaseB):
    def __init__(self):
        super().__init__()
        print("BaseClass2の初期化")

class DerivedClass(BaseClass1, BaseClass2):
    def __init__(self):
        super().__init__()
        print("DerivedClassの初期化")

print(DerivedClass.__mro__)

出力:

(<class '__main__.DerivedClass'>, <class '__main__.BaseClass1'>, <class '__main__.BaseA'>, <class '__main__.BaseClass2'>, <class '__main__.BaseB'>, <class 'object'>)

注意点: super().__init__()方式を使用する場合のみMROのロジックが使用されます。親クラス名を呼び出す方式の場合、MROとは関係ありません。

5.3.1 子クラスの多重継承でコンストラクタを定義しない場合

子クラスがコンストラクタを定義していない場合、子クラスはMROの順序に従い、最初の親クラスのコンストラクタのみが自動的に呼び出され、他の親クラスのコンストラクタは呼び出されません:

class BaseClass1:
    def __init__(self):
        print("親クラス1のコンストラクタが実行されました!")

class BaseClass2:
    def __init__(self):
        print("親クラス2のコンストラクタが実行されました!")

class DerivedClass(BaseClass1, BaseClass2):
    pass

derived = DerivedClass()
# 以下のみ出力されます:親クラス1のコンストラクタが実行されました!

5.3.2 子クラスの多重継承でコンストラクタをオーバーライドする場合

注意点: 子クラス内で親クラス名を使用して親クラスのコンストラクタを明示的に呼び出した場合、すべての親クラスのコンストラクタを呼び出さなくてもエラーにはなりません。なぜならPythonは選択的に親クラスのコンストラクタを呼び出すことをサポートしているからです。

多重継承の場合はsuper()方式を使用して親クラスのコンストラクタを呼び出すことをお勧めします。なぜなら、1つのsuper()だけですべての親クラスのコンストラクタを自動的に呼び出すことができ、1つずつ呼び出す必要がないからです:

class BaseClass1:
    def __init__(self):
        print("親クラス1のコンストラクタが実行されました!")

class BaseClass2:
    def __init__(self):
        print("親クラス2のコンストラクタが実行されました!")

class DerivedClass(BaseClass2, BaseClass1):
    def __init__(self):
        super().__init__()
        print("DerivedClassの初期化")

derived = DerivedClass()

出力:

親クラス2のコンストラクタが実行されました!
DerivedClassの初期化

なぜ子クラスがsuper()方式を使用したのに、1つの親クラスのコンストラクタしか呼び出されなかったのか説明します:

DerivedクラスのMRO順序:(<class '__main__.DerivedClass'>, <class '__main__.BaseClass2'>, <class '__main__.BaseClass1'>, <class 'object'>)

  1. super().__init__()がDerivedクラスのコンストラクタ内で呼び出され、MROに従ってBaseClass2のコンストラクタを呼び出します。
  2. しかし、BaseClass2内ではsuper()を使用していないため、さらに上に呼び出しません(super()メソッドに遭遇した場合のみMROロジックが使用されます)。

統合例:

class BaseA:
    def __init__(self):
        super().__init__()
        print("BaseAの初期化")

class BaseB:
    def __init__(self):
        print("BaseBの初期化")

class BaseClass1(BaseA):
    def __init__(self):
        super().__init__()
        print("BaseClass1の初期化")

class BaseClass2(BaseB):
    def __init__(self):
        super().__init__()
        print("BaseClass2の初期化")

class DerivedClass(BaseClass1, BaseClass2):
    def __init__(self):
        super().__init__()
        print("DerivedClassの初期化")

# Derivedクラスのインスタンスを作成
derived = DerivedClass()

DerivedクラスのMRO:

(<class '__main__.DerivedClass'>, <class '__main__.BaseClass1'>, <class '__main__.BaseA'>, <class '__main__.BaseClass2'>, <class '__main__.BaseB'>, <class 'object'>)

実行結果:

BaseBの初期化
BaseClass2の初期化
BaseAの初期化
BaseClass1の初期化
DerivedClassの初期化

注意点: 最上位のコンストラクタの呼び出しが完了すると、戻り始める必要があります。

タグ: Python クラス オブジェクト クロージャ デコレータ

8月2日 11:20 投稿