programowanieProgramista Python na poziomie średnim

Jak działa operator 'in' dla obiektów użytkownika w Pythonie? Co należy zaimplementować w klasie, aby wyrażenie 'x in your_obj' działało? Jak uniknąć problemów z wydajnością i niespodziewanych błędów?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

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.

Pytanie z podstępem.

Czy wystarczy zaimplementować tylko __iter__ lub tylko __getitem__, aby operator in działał poprawnie? Jak zmieni się zachowanie?

Odpowiedź:

  • Jeśli brakuje __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).
  • Takie zachowanie jest mniej efektywne i może spowodować nieskończone pętle lub wyjątki, jeśli metody zostały zaimplementowane z literówkami.

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

Przykłady rzeczywistych błędów z powodu niewiedzy o szczegółach tematu.


Historia

W jednym projekcie użytkownik kontener dla przechowywania bytów nadpisał tylko __iter__, zapominając zaimplementować __contains__. Operator in zaczął 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 sprawdzania x in obj z dużym x występowały długie pętle, a nawet Out Of Memory - ponieważ in sprawdza wszystkie indeksy w porządku rosnącym, aż napotka IndexError.


Historia

W jednym z projektów zrealizowano niestandardowy słownik, który dla in polegał 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).