Pythonにおける'シーケンス'プロトコルは、リストやタプルなどのインデックス付けや反復ができるオブジェクトのためのインターフェースを定義します。シーケンスは、データ構造に対する自然な操作をサポートするために、Pythonの初期バージョンから存在しました:インデックス付け、スライス、要素の反復。
問題 — ユーザ定義クラスがシーケンスのように振る舞うには、__iter__と__next__メソッドだけでは不十分です。シーケンスの動作を完全にサポートするには、追加のメソッドが必要です。
解決策 — 独自のシーケンスタイプを実装するには、getitem__メソッド(インデックス付けやスライスに必要)を定義し、オプションで__len(len()や長さのチェック用)を定義する必要があります。これにより、オブジェクトは反復、インデックスアクセス、スライス、さらにシーケンスに関するPythonの多くの標準操作をサポートします。
コード例:
class MyCounter: def __init__(self, stop): self._stop = stop def __getitem__(self, index): if 0 <= index < self._stop: return index * 10 else: raise IndexError('範囲外です') def __len__(self): return self._stop c = MyCounter(5) print(c[3]) # 30 print(len(c)) # 5 for x in c: print(x)
主な特徴:
__iter__と__next__のみを実装した場合、私のオブジェクトはシーケンス(Sequence)になりますか?
いいえ。そのようなオブジェクトは反復可能(iterable)ですが、シーケンスにはなりません。インデックス付け、スライス、リストライクオブジェクトの標準関数をサポートしません。
forループをサポートするには__getitem__を実装する必要がありますか?
必ずしも必要ではありません。__iter__が実装されていれば、forは機能します。しかし、__iter__がない場合、インタプリタは__getitem__を使用し、インデックス0から始めてIndexErrorが発生するまで続けます。したがって、シーケンスには__getitem__だけで十分です。
__getitem__をintに対してのみ実装し、sliceには対応しなくてもよいですか?
技術的には、c[0]には機能しますが、c[1:4]のスライスを取得しようとするとエラーが発生します。スライスをサポートするには、__getitem__はslice型のオブジェクトを処理できる必要があります(slice.indicesとisinstance(key, slice)を参照)。
コード例:
class S: def __getitem__(self, idx): if isinstance(idx, slice): return [x for x in range(idx.start or 0, idx.stop or 10, idx.step or 1)] return idx * 2
__iter__のみを定義したカスタム構造を実装し、スライスとインデックス付けができると思っています。
プラス面:
マイナス面:
クラスが__getitem__を実装し、スライスとintインデックスのサポート、および__len__も実装します。
プラス面:
マイナス面: