sec04 - クラス設計の実践:安全で拡張性のあるクラスを作るテクニック集
スポンサーリンク

クラスを使いこなすためのテクニック

この回では、Pythonのクラスをより安全で拡張性のある形で設計する方法を学びます。特殊メソッド、カプセル化、@property、コンストラクタ・デストラクタの使い方を順番に見ていきましょう。

特殊メソッド(マジックメソッド)

特殊メソッドの基本

Pythonでは、print()== などの操作を自分のクラスでも自然に使えるようにするための仕組みがあります。それが「特殊メソッド(マジックメソッド)」です。
__str__ を定義すると print(car) で分かりやすい文字列を表示でき、__eq__ を定義すると car1 == car2 のようにオブジェクト同士を比較できるようになります。

class Car:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

    def __str__(self):
        return f'{self.brand} {self.model}'

    def __eq__(self, other):
        return self.brand == other.brand and self.model == other.model

出力例

car1 = Car('Toyota', 'Corolla')
car2 = Car('Toyota', 'Corolla')
car3 = Car('Tesla', 'Model 3')

print(car1)         # Toyota Corolla
print(car3)         # Tesla Model 3
print(car1 == car2) # True
print(car1 == car3) # False

特殊メソッドの種類

今回の例で使用したもの:

  • __str__: print() で表示される文字列を定義
  • __eq__: == 演算子での比較を定義

その他の主な特殊メソッド(代表例):

  • __repr__: repr() や対話型での表示用
  • __add__, __sub__ など算術演算子
  • __len__: len() に対応
  • __getitem__, __setitem__: インデックス操作に対応
  • __iter__, __next__: イテレーション対応

特殊メソッドの全種類のリストはこちら(公式ドキュメント): https://docs.python.org/ja/3.14/reference/datamodel.html#emulating-callable-objects

スポンサーリンク

カプセル化とアクセス制御

クラスの中には、外部から触ってほしくないデータがあります。変数名の前に ___ を付けることでアクセス制御ができます。

  • _speed はあくまで「内部用」として扱い、直接外部から書き換えないことが推奨されます。
  • __engine_on は外部から直接触れないように隠しているので、必ず start_engine/stop_engine経由で操作します。

こうすることで、Carクラスのエンジンの状態や速度の管理を安全に行えます。

class Car:
    def __init__(self, speed):
        self._speed = speed       # 内部用目印
        self.__engine_on = False  # 強制的に隠す

    def start_engine(self):
        self.__engine_on = True
        print(f'{self.brand} {self.model}のエンジンを始動しました')

    def stop_engine(self):
        self.__engine_on = False
        print(f'{self.brand} {self.model}のエンジンを停止しました')


my_car = Car(50)
print(my_car._speed)  # 50 (アクセスできますが、推奨されません)
print(my_car.__engine_on)  # エラーが発生:AttributeError: 'Car' object has no attribute '__engine_on'

@property デコレーターで安全に属性を操作

@propertyを使うと、変数の「取得する処理」と「代入する処理」を同じ名前で統一できます。例えば、speedという変数を扱うように、値の取得と代入を行うことができます。通常の変数と違うところは、代入するときに「負の数は入れない」というようなチェックを入れることができます。これにより、変数_speedに直接触らず安全に操作できます。

  • getterメソッド
    • メソッドの定義の前に@propertyを付けます。
    • getterメソッドは値を返します。
  • setterメソッド
    • メソッドの定義の前に@メソッド名.setter(今回の場合は@speed.setter)を付けます。
    • メソッド名はgetterメソッドと同じです。
    • 代入される値が引数として渡されます。
    • 渡された値をチェックして、エラーを発生させるなどの処理を加えることができます。
class Car:
    def __init__(self, brand, model, speed):
        self.brand = brand
        self.model = model
        self._speed = speed  # 内部変数

    @property
    def speed(self):
        # 値を取得するための getter メソッド
        return self._speed

    @speed.setter
    def speed(self, value):
        # 値を設定する setter メソッド
        # 0未満はエラーとして扱う
        if value < 0:
            raise ValueError('速度は0以上で指定してください')
        else:
            self._speed = value

処理・出力例

car = Car('Toyota', 'Corolla', 50)
print(car.speed)  # 50

car.speed = 80
print(car.speed)  # 80

car.speed = -10   # ValueError: 速度は0以上で指定してください

コンストラクタ・デストラクタ

コンストラクタ(__init__)はインスタンス生成時に自動で呼ばれるメソッドで、初期設定や準備処理に使えます。デストラクタ(__del__)はインスタンス破棄時に自動で呼ばれ、終了処理やリソース解放に使います。

例えば通信クラスでは、__init__ で接続準備、__del__ で安全に接続を切断するイメージです。

class NetworkController:
    def __init__(self, host, port):
        self.host = host
        self.port = port
        print(f'{self.host}:{self.port}に接続準備をしました(コンストラクタ)')

    def __del__(self):
        print(f'{self.host}:{self.port}の接続を安全に切断しました(デストラクタ)')


nc = NetworkController('192.168.1.10', 8080)
del nc

出力例

192.168.1.10:8080に接続準備をしました(コンストラクタ)
192.168.1.10:8080の接続を安全に切断しました(デストラクタ)

デストラクタは、明示的にdel文を使用してオブジェクトを削除するか、関数などの処理が終了した時にPythonが自動的にオブジェクトを削除する時に実行されます。del nc行をコメントアウトして実行しても、デストラクタの処理が走ります。

実践例:安全で拡張性のあるクラス設計

共通操作はメソッドで制御し、属性は @property で安全に扱い、表示は __str__ で分かりやすくする。これらを組み合わせることで、安全で拡張性のあるクラス設計が可能です。

class Car:
    def __init__(self, brand, model, speed):
        self.brand = brand
        self.model = model
        self._speed = speed
        self.__engine_on = False

    def start_engine(self):
        self.__engine_on = True

    def stop_engine(self):
        self.__engine_on = False

    @property
    def speed(self):
        return self._speed

    @speed.setter
    def speed(self, value):
        # 0未満はエラーとして扱う
        if value < 0:
            raise ValueError('速度は0以上で指定してください')
        else:
            self._speed = value

    def __str__(self):
        return f'{self.brand} {self.model} ({self._speed}km/h)'
スポンサーリンク