Operator in w Pythonie określa, czy element znajduje się w kolekcji. Aby w obiektach użytkownika wspierać konstrukcję x in your_obj, należy zaimplementować metodę __contains__. Jeśli jej brakuje, interpreter spróbuje przeszukać obiekt za pomocą __iter__ lub __getitem__, ale zachowanie i wydajność mogą się różnić.
Przykład:
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
Jeśli zaimplementujesz tylko __iter__ (lub nawet tylko __getitem__), to in będzie działać, ale mniej efektywnie i czasami zupełnie nie tak, jak oczekiwano.
Zwróć uwagę: jeśli kolekcja jest ogromna, a sprawdzenie zaimplementowane naiwne (na przykład za pomocą pętli po całej liście), mogą wystąpić problemy z wydajnością. Do szybkich sprawdzeń używa się na przykład zbiorów.
Czy wystarczy zaimplementować tylko
__iter__lub tylko__getitem__, aby operatorindziałał poprawnie? Jak zmieni się zachowanie?
Odpowiedź:
__contains__, Python spróbuje przeszukać elementy, używając __iter__ (jeśli istnieje) lub __getitem__ (zaczynając od indeksu 0, aż do wystąpienia wyjątku IndexError).Przykład:
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
Historia
W jednym projekcie użytkownik kontener dla przechowywania bytów nadpisał tylko
__iter__, zapominając zaimplementować__contains__. Operatorinzaczął działać nie tylko wolno (dla dużych kolekcji zauważalne były lagi), ale również nagle łamał się z tajemniczymi błędami, gdy iterator błędnie wyrzucał wyjątki innego typu niż StopIteration.
Historia
Dla klasy, w której elementy były obliczane "w locie" po indeksie, deweloper zaimplementował tylko
__getitem__. Podczas próby sprawdzaniax in objz dużym x występowały długie pętle, a nawet Out Of Memory - ponieważinsprawdza wszystkie indeksy w porządku rosnącym, aż napotka IndexError.
Historia
W jednym z projektów zrealizowano niestandardowy słownik, który dla
inpolegał tylko na__iter__. Doprowadziło to do sytuacji, w której dla 100 000 kluczy wyszukiwanie zajmowało sekundy w porównaniu do milisekund w standardowym dict (gdzie__contains__jest efektywnie zaimplementowane).