問題の歴史:
シーケンスはPythonにおける最も古く、基本的な概念の一つです。古典的な例には、リスト(list)、文字列(str)、タプル(tuple)があります。シーケンスとやり取りするためには、特別なプロトコルがあり、__getitem__と__len__というメソッドを実装する必要があります。これによりオブジェクトは「シーケンスのように」振る舞い、インデクシング、スライス、ループでの使用、さらにはいくつかの標準関数をサポートすることができます。
問題:
これらのメソッドを正しく実装しなければ、ユーザーのクラスはインデクシングやforループ、len()のような関数と連携できません。初心者の開発者は、これらのメソッドのうちの一つしか実装せず、例外処理を考慮せず、スライスのサポートを実装しないことが多く、予期しない動作や不正確な結果を得ることになります。
解決法:
インデクシングとスライスをサポートするために__getitem__(self, key)メソッドを実装し、len()関数や正しい反復可能性のために__len__(self)も実装する必要があります。スライスをサポートするためには、__getitem__内でkeyのタイプを区別し、スライスオブジェクトを適切に処理する必要があります。
コードの例:
def is_even(n): return n % 2 == 0 class EvenSequence: def __init__(self, size): self.size = size def __getitem__(self, index): if isinstance(index, slice): return [x for x in range(self.size)[index] if is_even(x)] if index < 0 or index >= self.size: raise IndexError('Index out of bounds') return index if is_even(index) else None def __len__(self): return self.size
重要な特徴:
オブジェクトをシーケンスとして機能させるには、__getitem__メソッドだけで十分ですか?
部分的に。__getitem__のみを実装すれば、オブジェクトをforでイテレートすることができ、要素にインデクシングできますが、len()は機能しません。
コードの例:
class SeqOnlyGetitem: def __getitem__(self, index): if index >= 10: raise IndexError return index * 2 s = SeqOnlyGetitem() for x in s: print(x) # 動作します(イテレート可能) # print(len(s)) # TypeError エラー
負のインデックスを処理するには?無視した場合どうなるのでしょうか?
負のインデックスはPythonにおけるシーケンスの重要な特徴です。これを処理しないと、オブジェクトはユーザーにとって予期しない動作をします。
class NegIndex: def __init__(self, data): self.data = data def __getitem__(self, index): if index < 0: index += len(self.data) return self.data[index]
自分のSequenceクラスに__contains__や__iter__を実装する必要がありますか?
必ずしも必要ではありません。__getitem__と__len__が実装されていれば、in文は機能し、forもこれらを使ってイテレートします。これらのメソッドを直接実装する必要は通常ありませんが、パフォーマンスの向上には繋がる可能性があります。
開発者は自分のクラスのために__getitem__だけを実装し、正のインデックスのみに対応しました。モジュールは一部のユニットテストを通過しましたが、実際の使用で存在しないインデックスや負のインデックスを取得しようとした際にすべてが壊れました。
長所:
短所:
チームは両方のメソッド(getitem__と__len)を実装し、スライス、負のインデックスを考慮し、正しい例外を投げました。最終的なクラスはPythonのすべての標準的なケースで機能しました。
長所:
短所: