Pythonクラスのカスタマイズ:getitemなどの特殊メソッドで添字アクセスを実装する方法

Pythonのリストや辞書のように、自作のクラスに対しても obj[0]obj["key"] といった添字操作(ブラケット記法)を使いたい場合があります。

これを実現するには、クラス定義の中で特定の特殊メソッドを実装する必要があります。これにより、オブジェクトをコンテナ(データの入れ物)のように振る舞わせることができます。

この記事では、添字操作を実現するために必要な4つの特殊メソッドと、その実装例について解説します。

目次

添字操作に必要な4つの特殊メソッド

オブジェクトをリストや辞書のように操作するために、以下のメソッドを定義します。

  1. __len__(self): len(obj) が実行されたときの動作。要素数を返します。
  2. __getitem__(self, key): obj[key] で参照したときの動作。値の取得を担当します。
  3. __setitem__(self, key, value): obj[key] = value で代入したときの動作。値の更新や追加を担当します。
  4. __delitem__(self, key): del obj[key] が実行されたときの動作。要素の削除を担当します。

実装例:高機能なチームメンバー管理クラス

例として、チームメンバーの名前をリストで管理する Team クラスを作成します。このクラスは、単なるリストの機能に加えて、以下のカスタマイズを行います。

  • インデックス(番号)によるメンバーの取得・更新・削除。
  • メンバー名のバリデーション(文字列以外はエラー)。
  • 文字列(名前)を指定して、そのメンバーの管理番号(インデックス)を取得する機能。
class Team:
    def __init__(self, *members):
        """初期化:可変長引数でメンバーを受け取りリストに格納"""
        self._members = list(members)

    def __len__(self):
        """len(obj) の挙動:メンバー数を返す"""
        return len(self._members)

    def __getitem__(self, key):
        """obj[key] の挙動:値の取得"""
        # インデックス(整数)の場合
        if isinstance(key, int):
            if 0 <= key < len(self._members):
                return self._members[key]
            else:
                raise IndexError("指定されたインデックスは範囲外です。")
        
        # 文字列(名前)の場合:そのメンバーのインデックスを返す(辞書的な挙動)
        elif isinstance(key, str):
            if key in self._members:
                return self._members.index(key)
            else:
                raise ValueError(f"メンバー '{key}' は存在しません。")
        
        else:
            raise TypeError("インデックスは整数または文字列である必要があります。")

    def __setitem__(self, index, value):
        """obj[index] = value の挙動:値の更新"""
        # ここではインデックス指定のみ許可する設計とする
        if not isinstance(index, int):
            raise TypeError("更新時のインデックスは整数で指定してください。")
            
        if not isinstance(value, str):
            raise TypeError("メンバー名は文字列である必要があります。")

        if 0 <= index < len(self._members):
            print(f"[更新] {self._members[index]} を {value} に変更します。")
            self._members[index] = value
        else:
            raise IndexError("指定されたインデックスは範囲外です。")

    def __delitem__(self, index):
        """del obj[index] の挙動:要素の削除"""
        if 0 <= index < len(self._members):
            removed = self._members.pop(index)
            print(f"[削除] {removed} をチームから削除しました。")
        else:
            raise IndexError("指定されたインデックスは範囲外です。")

    def __repr__(self):
        """オブジェクトの文字列表現"""
        return f"Team({self._members})"

# --- 動作確認 ---

# チームの作成
my_team = Team("Tanaka", "Sato", "Suzuki", "Takahashi")
print(f"初期状態: {my_team}")
print(f"人数: {len(my_team)}")

# 1. __getitem__ (インデックス参照)
print(f"2番目のメンバー: {my_team[1]}")

# 2. __getitem__ (文字列キーでの参照 - カスタム機能)
# 名前からインデックスを逆引きする
try:
    idx = my_team["Suzuki"]
    print(f"'Suzuki' のインデックス: {idx}")
except ValueError as e:
    print(e)

# 3. __setitem__ (更新)
# バリデーションにより、文字列以外を代入しようとするとエラーになる
try:
    my_team[0] = "Yamada" # 正常な更新
    # my_team[0] = 123    # エラーになる(TypeError)
except TypeError as e:
    print(e)

print(f"更新後: {my_team}")

# 4. __delitem__ (削除)
del my_team[2] # "Suzuki" を削除
print(f"削除後: {my_team}")

実行結果:

初期状態: Team(['Tanaka', 'Sato', 'Suzuki', 'Takahashi'])
人数: 4
2番目のメンバー: Sato
'Suzuki' のインデックス: 2
[更新] Tanaka を Yamada に変更します。
更新後: Team(['Yamada', 'Sato', 'Suzuki', 'Takahashi'])
[削除] Suzuki をチームから削除しました。
削除後: Team(['Yamada', 'Sato', 'Takahashi'])

解説:柔軟な __getitem__ の実装

上記のコードで特に注目すべき点は、__getitem__ メソッド内での型判定(isinstance)です。

def __getitem__(self, key):
    if isinstance(key, int):
        # 整数の場合、リストのインデックスとして処理
        ...
    elif isinstance(key, str):
        # 文字列の場合、辞書のキーのように処理(ここでは検索)
        ...

このように実装することで、一つのクラスに対して「リストのような操作(obj[0])」と「辞書のような操作(obj["name"])」の両方をサポートさせることができます。

まとめ

  • 自作クラスで [] を使った操作を行うには、以下の特殊メソッドを実装します。
    • __getitem__: 参照
    • __setitem__: 代入
    • __delitem__: 削除
  • __len__ を実装することで、len() 関数に対応できます。
  • __getitem__ 内で引数 key の型を判定することで、インデックス番号だけでなく、文字列キーなど柔軟なアクセス方法を提供できます。

これらを活用することで、Pythonの組み込み型と同じように直感的に扱えるクラスを設計できます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次