Pythonのリストや辞書のように、自作のクラスに対しても obj[0] や obj["key"] といった添字操作(ブラケット記法)を使いたい場合があります。
これを実現するには、クラス定義の中で特定の特殊メソッドを実装する必要があります。これにより、オブジェクトをコンテナ(データの入れ物)のように振る舞わせることができます。
この記事では、添字操作を実現するために必要な4つの特殊メソッドと、その実装例について解説します。
目次
添字操作に必要な4つの特殊メソッド
オブジェクトをリストや辞書のように操作するために、以下のメソッドを定義します。
__len__(self):len(obj)が実行されたときの動作。要素数を返します。__getitem__(self, key):obj[key]で参照したときの動作。値の取得を担当します。__setitem__(self, key, value):obj[key] = valueで代入したときの動作。値の更新や追加を担当します。__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の組み込み型と同じように直感的に扱えるクラスを設計できます。
