概要(getitemは「obj[i]やスライス」を使えるようにする特別メソッド)
getitemは、obj[i] や obj[a:b] のようなインデックス・スライスアクセスを可能にする入口です。シーケンスっぽいクラス(list風・文字列風・配列風)を自作するときに不可欠で、「何を返すか」「範囲外をどう扱うか」「スライスはどう返すか」を決めるのが核心です。インデックスは通常0始まり、範囲外はIndexErrorを投げる、スライスはsliceオブジェクトを受け取り適切に処理する、というのが基本ルールです。
基本の使い方(インデックスと範囲外の扱い)
最小の実装と動作
class Cart:
def __init__(self):
self._items: list[tuple[str, int]] = [] # (SKU, 数量)
def add(self, sku: str, qty: int):
self._items.append((sku, qty))
def __getitem__(self, index: int) -> tuple[str, int]:
# listに委譲するだけでもOK(IndexErrorはlistが投げる)
return self._items[index]
c = Cart()
c.add("A001", 2)
c.add("B002", 1)
print(c[0]) # ('A001', 2)
print(c[1]) # ('B002', 1)
# print(c[2]) # IndexError(範囲外)
Python「範囲外はIndexError」「型が不正ならTypeError」を守ると、Python標準の振る舞いと揃い、ユーザーが迷いません。
型が不正なキーへの対応(TypeError)
class Cart:
def __init__(self):
self._items = []
def add(self, sku, qty):
self._items.append((sku, qty))
def __getitem__(self, index):
if not isinstance(index, int):
raise TypeError("indexはint")
return self._items[index]
Python数値以外の「キー」を受け付けない設計なら、TypeErrorで早めに弾きます。
スライスの扱い(sliceオブジェクトを受けて部分列を返す)
スライスに対応する基本パターン
class Cart:
def __init__(self):
self._items: list[tuple[str, int]] = []
def add(self, sku: str, qty: int):
self._items.append((sku, qty))
def __getitem__(self, key):
if isinstance(key, int):
return self._items[key] # 負のインデックスもlistが処理
if isinstance(key, slice):
# スライスは同じ型で返すか、listで返すかを決める
new = Cart()
for sku, qty in self._items[key]:
new.add(sku, qty)
return new
raise TypeError("keyはintまたはsliceに対応")
c = Cart()
for i in range(5):
c.add(f"SKU{i}", i+1)
sub = c[1:4] # Cartを返す(1〜3番目)
print(sub[0]) # ('SKU1', 2)
Pythonスライスを「同じ型で返す」か「listで返す」かは設計次第です。自作シーケンスなら「同じ型で返す」と使い心地が良くなります。
slice.indicesで正規化してから処理する
class Buffer:
def __init__(self, text: str = ""):
self._text = text
def __getitem__(self, key):
if isinstance(key, int):
return self._text[key]
if isinstance(key, slice):
start, stop, step = key.indices(len(self._text))
return self._text[start:stop:step]
raise TypeError("keyはintまたはslice")
Pythonindicesは負の値や範囲外を安全な範囲に正規化してくれるため、実装が安定します。
シーケンス設計の型(len・負インデックス・不変条件)
lenを揃える(サイズの一貫性)
class Inventory:
def __init__(self):
self._set: set[str] = set()
def add(self, sku: str):
self._set.add(sku)
def __len__(self) -> int:
return len(self._set)
def __getitem__(self, index: int) -> str:
# setは順序がないので、listに変換して順序を決める
return list(self._set)[index]
Pythonlen()が直感どおりの「要素数」を返すようにしておくと、インデックスとも整合します。順序のない集合をインデックスで扱う場合は、並びの定義(ソートなど)を明記するのが安全です。
負のインデックス・ステップの対応
Pythonの標準シーケンスは、-1が末尾、-2が末尾のひとつ前…と扱います。listや文字列に委譲する実装なら自然に対応できます。自前で配列を持つ場合も「index < 0 なら len + index」を使って負インデックスに対応するのが定石です。
不変条件(範囲外はIndexError、型不正はTypeError)
この2つは“空気のような契約”。守るだけで、forループ・in演算子・スライス連鎖など標準機能との相性が格段に良くなります。
応用と関連メソッド(setitem・delitem・イテレーション)
書き込み・削除にも対応する
class Store:
def __init__(self):
self._items: list[str] = []
def __getitem__(self, i: int) -> str:
return self._items[i]
def __setitem__(self, i: int, v: str) -> None:
if not v: raise ValueError("空不可")
self._items[i] = v
def __delitem__(self, i: int) -> None:
del self._items[i]
Pythonミュータブルなシーケンスにしたいなら、setitem と delitem を合わせて実装します。スライスの書き込み・削除もサポートできます(listに委譲すると楽です)。
イテレーションとの関係
現代的には iter を実装するのが推奨ですが、古いプロトコル(0から順に getitem を呼び、IndexErrorで停止)でもforループが動きます。パフォーマンスと可読性のため、iterの実装を用意するのが良い設計です。
class Cart:
def __init__(self):
self._items = []
def add(self, sku, qty):
self._items.append((sku, qty))
def __getitem__(self, i):
return self._items[i]
def __len__(self):
return len(self._items)
def __iter__(self):
return iter(self._items) # 標準の反復子を返す
Python例題(テキスト行バッファと商品カートの堅牢なgetitem)
行バッファ:行数とスライスを自然に扱う
class TextLines:
def __init__(self):
self._lines: list[str] = []
def add(self, line: str):
if "\n" in line:
raise ValueError("1行に改行は不可")
self._lines.append(line)
def __len__(self) -> int:
return len(self._lines)
def __getitem__(self, key):
if isinstance(key, int):
return self._lines[key] # 負インデックスOK(listに委譲)
if isinstance(key, slice):
return self._lines[key] # listをそのまま返す設計
raise TypeError("keyはintまたはslice")
Pythonカート:同型で部分カートを返す(スライスは新インスタンス)
class Cart:
def __init__(self):
self._items: list[tuple[str, int]] = []
def add(self, sku: str, qty: int):
if qty <= 0:
raise ValueError("数量は正の数")
self._items.append((sku, qty))
def __len__(self) -> int:
return len(self._items)
def __getitem__(self, key):
if isinstance(key, int):
return self._items[key]
if isinstance(key, slice):
out = Cart()
for sku, qty in self._items[key]:
out.add(sku, qty)
return out
raise TypeError("keyはintまたはslice")
Pythonこの設計なら、c[1:4] の結果をそのまま別の処理に渡せるため、扱いやすさと安全性が両立します。
まとめ(getitemは「インデックスとスライスの入口」。標準の契約を守る)
getitemを実装すると、obj[i]・obj[a:b]・forループなど、Pythonらしい操作が自作クラスでも自然に使えます。範囲外はIndexError、型不正はTypeError、インデックスは0始まり、負インデックスとスライスに対応——この標準契約を守り、lenやiterと整合させる。スライスは同型で返すか、listで返すかを明確に決め、性能面では基盤構造(listや文字列)に委譲してO(1)〜O(n)の直感的なコストに保つ。これを徹底すれば、初心者でも「読みやすく、拡張しやすい」シーケンス設計を自然に書けるようになります。
