Pythonのオーバーライドについて

はじめに

最近仕事で社内共通テンプレートの振る舞いを変えて、仕事の目的を達成する必要があった。

そのときに必要になったのが、オーバーライド。

親クラスで定義したプロパティに、オーバーライドした子クラスからアクセスできなかったりしたので、この記事では、Pythonにおけるオーバーライドについて整理していきたい。

ソースコードは、ChatGPTの力もお借りした。

superで親クラスのコンストラクタを呼び出す

以下の例では、Childクラスのインスタンス生成時に、親クラスのコンストラクタも呼び出すことができている。

class Parent:
    def __init__(self, name):
        self.name = name
        print(f"Parent initialized with name: {self.name}")

class Child(Parent):
    def __init__(self, name, age):
        super().__init__(name)  # 親クラスの__init__を呼び出し
        self.age = age
        print(f"Child initialized with age: {self.age}")

child = Child("Alice", 10)

Childクラスについて見ていこう。class Child(Parent)と書くことで、Parentクラスを継承することが出来る。コンストラクタの中で、super().__init__(name)を行い、Parentクラスのコンストラクタを呼び出している。そのため、呼び出した親クラスのコンストラクタでnameが初期化され、子クラスのコンストラクタではageが初期化されている。ちなみに、コンストラクタとは、__init__メソッドのことである。

class Child(Parent):
    def __init__(self, name, age):
        super().__init__(name)  # 親クラスの__init__を呼び出し
        self.age = age
        print(f"Child initialized with age: {self.age}")

出力結果

Parent initialized with name: Alice
Child initialized with age: 10

superで親クラスのメソッドを呼び出す

以下の例では、Childクラスのインスタンスを生成して、greetメソッドを呼び出している。Childクラスのsuper().greet()で、親クラスのgreetメソッドを呼び出している。そうすることで、親クラスで行っている「Hello from Parent」が表示されている。

class Parent:
    def greet(self):
        print("Hello from Parent")

class Child(Parent):
    def greet(self):
        print("Hello from Child")
        super().greet()  # 親クラスのgreetメソッドを呼び出す

child = Child()
child.greet()

出力結果

Hello from Child
Hello from Parent

昨日解決できなかった問題の解決の糸口が見つかったかもしれない

親クラスで定義しているプロパティに子クラスからアクセスできないという問題。

その解決の糸口が見つかった可能性がある。

GPTに相談したら以下の回答が得られた。

確かに、親クラスでプロパティをプライベート変数として定義していた。

その場合、名前がマングリング(変更)されるらしい。

そして、子クラスから直接アクセスできない。

ソースコードの例

class Parent:
    def __init__(self):
        self.__hidden_property = "This is a private property"

    @property
    def hidden_property(self):
        return self.__hidden_property

    @hidden_property.setter
    def hidden_property(self, value):
        self.__hidden_property = value


class Child(Parent):
    def __init__(self):
        super().__init__()

    def access_parent_property(self):
        # 試しに親クラスのプライベートプロパティに直接アクセス
        try:
            return self.__hidden_property  # ここでエラーが発生
        except AttributeError as e:
            return f"Error: {e}"

    def override_parent_property(self):
        # 子クラスでプロパティをオーバーライド
        self.hidden_property = "Overridden value"


# 実行例
child = Child()

# 親クラスのプロパティにアクセスを試みる
print("アクセス試行:", child.access_parent_property())

# 親クラスのプロパティをオーバーライド
child.override_parent_property()
print("プロパティの値:", child.hidden_property)

# プライベート変数には依然としてアクセスできない
try:
    print(child.__hidden_property)
except AttributeError as e:
    print(f"直接アクセス失敗: {e}")

実行結果

アクセス試行: Error: 'Child' object has no attribute '__hidden_property'
プロパティの値: Overridden value
直接アクセス失敗: 'Child' object has no attribute '__hidden_property'

親クラスでプライベート変数として定義された__hidden_propertyに、子クラスからアクセスすることはできない。

名前がマングリングされているとあった。どのように変更されているのか、実際の値を見ていこう。

デバッグコンソールで処理を止めて、childインスタンスの中身を見てみた。

親クラスのコンストラクタで以下のように定義された__hidden_propertyの名前が_Parent__hidden_propertyに変更されていることが確認できた。

class Parent:
    def __init__(self):
        self.__hidden_property = "This is a private property"

Childクラスで、self.__hidden_propertyと書いたものは、_Child__hidden_propertyと名前がわかっていた。どうやら、プライベート変数の場合、_のあとにクラス名がついたものが先頭につくらしい。

子クラスから親クラスのプライベート変数にアクセスするには、子クラスでプロパティをオーバーライドするか、名前が変更された後の変数名を使用する(推奨されない)。

オーバーライドでは、まず、Childクラスで以下のようにオーバーライドするメソッドを作成する。

class Child(Parent):
    def __init__(self):
        super().__init__()

    def access_parent_property(self):
        # 試しに親クラスのプライベートプロパティに直接アクセス
        try:
            return self.__hidden_property  # ここでエラーが発生
        except AttributeError as e:
            return f"Error: {e}"

    def override_parent_property(self):
        # 子クラスでプロパティをオーバーライド
        self.hidden_property = "Overridden value"

前提として、親クラスでプライベート変数を返すプロパティ関数が定義されている必要がある。

class Parent:
    def __init__(self):
        self.__hidden_property = "This is a private property"

    @property
    def hidden_property(self):
        return self.__hidden_property

    @hidden_property.setter
    def hidden_property(self, value):
        self.__hidden_property = value

これで、childインスタンスに対して、override_parent_propertyメソッドを使用することで、hidden_propertyの値をオーバーライドできる。

# 実行例
child = Child()

# 親クラスのプロパティにアクセスを試みる
print("アクセス試行:", child.access_parent_property())

# 親クラスのプロパティをオーバーライド
child.override_parent_property()
print("プロパティの値:", child.hidden_property)

実行結果

アクセス試行: Error: 'Child' object has no attribute '__hidden_property'
プロパティの値: Overridden value
直接アクセス失敗: 'Child' object has no attribute '__hidden_property'

名前が変更したとの親クラスのプライベート変数をフルネームで指定することも可能(ただし、推奨されない)。

プライベート変数の目的は以下の通り。外部からの不要なアクセスを防ぐために存在する。コード量が多くなると効果を実感できるだろう。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

投稿ID : 26860