Pythonの'in'演算子は、要素がコレクションに含まれているかどうかを判断します。ユーザーオブジェクトの場合、x in your_obj構文をサポートするためには、メソッド__contains__を実装する必要があります。これがない場合、インタープリターは__iter__または__getitem__を使用してオブジェクトを反復しようとしますが、動作と効率は異なる可能性があります。
例:
class MyBag: def __init__(self, items): self.items = items def __contains__(self, value): return value in self.items bag = MyBag([1,2,3]) print(2 in bag) # True print(5 in bag) # False
もし__iter__(または__getitem__)だけを実装した場合でも、inは動作しますが、効率は低く、時には期待通りに動作しないことがあります。
注意: コレクションが非常に大きい場合、チェックが単純に実装されていると(例えば、リスト全体をループすることによって)、パフォーマンスの問題が発生する可能性があります。迅速なチェックのためには、例えば集合を使用します。
'in'演算子の正しい動作のために、
__iter__または__getitem__だけを実装すれば十分ですか?動作はどのように変わりますか?
答え:
__contains__がない場合、Pythonは(存在する場合)__iter__を使って要素を反復しようとします、または__getitem__(インデックス0から開始し、IndexErrorが発生するまで)を使います。例:
class Weird: def __getitem__(self, idx): if idx < 3: return idx raise IndexError w = Weird() print(2 in w) # True print(5 in w) # False
物語
あるプロジェクトで、エンティティを格納するためのユーザー定義コンテナは、
__iter__のみをオーバーライドし、__contains__を実装するのを忘れました。'in'演算子は遅く機能するようになり(大きなコレクションではラグが目立つ)、イテレータが誤ってStopIterationタイプではない例外をスローする際に突然エラーが発生しました。
物語
要素がインデックスで「その場で」計算されるクラスのために、開発者は
__getitem__のみを実装しました。大きなxでx in objをチェックしようとすると、長いループが発生し、Out Of Memoryが発生しました。なぜなら、inはIndexErrorに遭遇するまで増加するすべてのインデックスをチェックするためです。
物語
あるプロジェクトでは、'in'に関しては
__iter__のみに依存するカスタム辞書が実装されました。これにより、100,000のキーに対する検索が、標準のdict(ここでは__contains__が効率的に実装されている)に対してミリ秒ではなく秒を費やしました。