programowanieProgramista Backend

Jak zaimplementować interfejs sekwencji (Sequence) w Pythonie? Po co realizować specjalne metody __getitem__, __len__ i jakie pułapki istnieją?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Historia pytania:

Sekwencje to jedna z najstarszych i najbardziej podstawowych koncepcji w Pythonie. Klasyczne przykłady to listy (list), łańcuchy (str) i krotki (tuple). Do interakcji z sekwencjami wprowadzono specjalny protokół: konieczne jest zaimplementowanie metod __getitem__ i __len__. Dzięki temu obiekt zachowuje się "jak sekwencja" — wspiera indeksowanie, wycinki, pracę w pętlach i niektóre standardowe funkcje.

Problem:

Bez właściwej implementacji tych metod, klasa użytkownika nie będzie mogła działać w operacjach indeksowania, w pętli for oraz w funkcjach takich jak len(). Często początkujący programiści implementują tylko jedną z metod, nie biorąc pod uwagę obsługi wyjątków, nie implementują wsparcia dla wycinków (slice), co prowadzi do nieprawidłowego lub nieoczekiwanego zachowania.

Rozwiązanie:

Należy zaimplementować metodę __getitem__(self, key) do wsparcia indeksacji i wycinków, a także __len__(self) do używania funkcji len() i prawidłowej iterowalności. Aby wspierać wycinki, należy rozróżniać typ key w __getitem__ i poprawnie obsługiwać obiekty slice.

Przykład kodu:

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 poza zakresem') return index if is_even(index) else None def __len__(self): return self.size

Kluczowe cechy:

  • Interakcja z większością funkcji i składni Pythona, jeśli oba metody są zaimplementowane.
  • Aby wspierać wycinki, należy obsługiwać obiekty typu slice w getitem.
  • Nie implementując len, obiekt nie będzie działał z len() i pełnym zestawem niektórych standardowych funkcji.

Pytania z podstępem.

Czy można ograniczyć się tylko do metody getitem, aby obiekt działał jako sekwencja?

Częściowo. Jeśli zaimplementuje się tylko __getitem__, można będzie iterować przez obiekt za pomocą for i indeksować elementy, ale len() nie będzie działać.

Przykład kodu:

class SeqOnlyGetitem: def __getitem__(self, index): if index >= 10: raise IndexError return index * 2 s = SeqOnlyGetitem() for x in s: print(x) # Działa (iterowalne) # print(len(s)) # Błąd TypeError

Jak obsługiwać indeksy ujemne i co się stanie, jeśli ich nie uwzględnimy?

Indeksy ujemne to kluczowa cecha sekwencji w Pythonie. Jeśli ich nie obchodzić, obiekt zachowuje się w sposób nieoczekiwany dla użytkownika.

class NegIndex: def __init__(self, data): self.data = data def __getitem__(self, index): if index < 0: index += len(self.data) return self.data[index]

Czy należy implementować contains lub iter dla swojej klasy Sequence?

Nie jest to konieczne. Jeśli zaimplementowane są __getitem__ i __len__, funkcja in będzie działać, a for będzie je wykorzystywać do iteracji. Implementacja tych metod zwykle nie jest wymagana, ale może poprawić wydajność.

Typowe błędy i antywzorce

  • Nie sprawdzają zakresu indeksów, co prowadzi do IndexError lub nieoczekiwanych wyników.
  • Nie implementują wsparcia dla wycinków (slice), dlatego obj[2:10:2] powoduje błąd.
  • Zapominają o indeksach ujemnych (obj[-1]).

Przykład z życia

Negatywny przypadek

Programista zaimplementował tylko getitem dla swojej klasy tylko dla dodatnich indeksów. Moduł przeszedł część testów jednostkowych, ale przy rzeczywistym użyciu, próbując wziąć nieistniejący indeks lub indeks ujemny, wszystko się łamało.

Zalety:

  • Szybka podstawowa implementacja.

Wady:

  • Nieoczekiwane błędy w praktyce.
  • Niewygodność użytkowania (nie można wziąć ostatnich elementów przez indeks ujemny, nie działa wycinek, nie działa len()).

Pozytywny przypadek

Zespół zaimplementował obie metody (getitem i len), uwzględnił wycinki, indeksy ujemne, wyrzucał poprawne wyjątki. Końcowa klasa działała we wszystkich standardowych przypadkach Pythona.

Zalety:

  • Przewidywalne zachowanie z perspektywy API Pythona.
  • Wygoda użytkowania, minimalna liczba błędów.

Wady:

  • Trochę więcej kodu, wymaga uwagi na szczegóły podczas projektowania.