programowanieBackend Developer

Wyjaśnij protokół 'Sequence' w Pythonie, jak go zaimplementować i czym różni się od zwykłych obiektów iterowalnych?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

W Pythonie protokół 'Sequence' definiuje interfejs dla obiektów, które można indeksować i iterować, takich jak listy lub krotki. Historycznie, sekwencje pojawiły się praktycznie od pierwszych wersji Pythona, aby wspierać naturalne operacje na strukturach danych: indeksowanie, wycinki, iteracja elementów.

Problem — aby klasa użytkownika zachowywała się jak sekwencja, samo zaimplementowanie metod iter i next nie wystarczy. Aby w pełni wspierać zachowanie sekwencji, niezbędne są dodatkowe metody.

Rozwiązanie — aby zaimplementować własny typ sekwencji, należy zdefiniować metody getitem (niezbędna do indeksowania i wycinków) i opcjonalnie len (do len() i sprawdzania długości). W ten sposób obiekt będzie wspierać iterację, dostęp po indeksie, pracę z wycinkami, a także wiele standardowych operacji Pythona z sekwencjami.

Przykład kodu:

class MyCounter: def __init__(self, stop): self._stop = stop def __getitem__(self, index): if 0 <= index < self._stop: return index * 10 else: raise IndexError('Poza zakresem') def __len__(self): return self._stop c = MyCounter(5) print(c[3]) # 30 print(len(c)) # 5 for x in c: print(x)

Kluczowe cechy:

  • Obiekt wspiera dostęp po indeksie i wycinki poprzez getitem.
  • Aby wspierać funkcje len(), potrzebny jest len.
  • Iteracja opiera się na getitem, a nie na iter, jeśli iter nie jest zdefiniowany.

Pytania z haczykiem.

Czy jeśli zaimplementuję tylko iter i next, mój obiekt stanie się sekwencją (Sequence)?

Nie. Taki obiekt będzie tylko iterowalnym (iterable), ale nie sekwencją. Nie będzie wspierać indeksowania, wycinków ani standardowych funkcji podobnych do list.

Czy konieczne jest zaimplementowanie getitem do wsparcia pętli for?

Nie jest to konieczne. Jeśli została zaimplementowana iter, for będzie działać. Ale jeśli nie ma iter, interpreter spróbuje użyć getitem, zaczynając od indeksu 0, aż napotka IndexError. Dlatego dla sekwencji wystarczy getitem.

Czy można zaimplementować getitem tylko dla int, a nie dla slice?

Technicznie obiekt będzie działał dla c[0], ale próba wzięcia wycinka c[1:4] nie zadziała. Aby wspierać wycinki, getitem musi umieć obsługiwać obiekty typu slice (zobacz slice.indices i isinstance(key, slice)).

Przykład kodu:

class S: def __getitem__(self, idx): if isinstance(idx, slice): return [x for x in range(idx.start or 0, idx.stop or 10, idx.step or 1)] return idx * 2

Typowe błędy i antywzorce

  • Definiują tylko iter, oczekując, że obiekt stanie się pełnoprawną sekwencją.
  • Realizują getitem, ale nie obsługują obiektów slice.
  • Nie implementują len, przez co len(obj) wywołuje TypeError.

Przykład z życia

Negatywny przypadek

Zaimplementowano niestandardową strukturę, definiując tylko iter, myśląc, że teraz można używać wycinków i indeksacji.

Zalety:

  • Działa w pętli for i obsługuje generatory.

Wady:

  • Brak wsparcia dla składni obj[5] lub obj[1:3], powoduje błąd.
  • len(obj) również nie działa.

Pozytywny przypadek

Klasa implementuje getitem z obsługą pracy z wycinkami i indeksami int, a także len.

Zalety:

  • Wspierane są wszystkie operacje sekwencji: wycinki, indeksacja, len, iteracja.
  • Integracja ze standardową biblioteką (np. random.choice).

Wady:

  • Należy dokładnie zaimplementować obsługę slice, w przeciwnym razie mogą wystąpić błędy przy pracy z wycinkami.