programowanieBackend developer

Jak działa wbudowana funkcja zip() w Pythonie, do czego jest używana i jakie są pułapki przy przetwarzaniu sekwencji o różnej długości?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Historia pytania

Funkcja zip() pojawiła się już w Pythonie 2 (wtedy zwracała listę), a w Pythonie 3 zwraca leniwy iterator. "Łączy" kilka sekwencji w krotki element po elemencie, co uczyniło przetwarzanie równoległych kolekcji wygodnym i efektywnym.

Problem

Często trzeba przetwarzać kilka list (lub innych rodzajów sekwencji) jednocześnie — na przykład przejść przez parę klucz-wartość lub przetworzyć pary współrzędne. Samodzielna synchronizacja indeksów jest źródłem błędów i nieczytelności kodu, szczególnie dla kolekcji o różnej długości.

Rozwiązanie

Funkcja zip() przyjmuje dowolną liczbę obiektów iterowalnych i zwraca iterator krotek, z których każda zawiera odpowiednie elementy każdego z iterowalnych. Jeśli sekwencje mają różną długość, wynik jest obcinany do najkrótszej.

Przykład kodu:

names = ['Alice', 'Bob', 'Charlie'] ages = [24, 27, 30] for name, age in zip(names, ages): print(f'{name} ma {age} lat')

Można rozwinąć zip za pomocą *:

pairs = [(1, 'a'), (2, 'b'), (3, 'c')] nums, chars = zip(*pairs) print(nums) # (1, 2, 3) print(chars) # ('a', 'b', 'c')

Kluczowe cechy:

  • zip() zwraca iterator (w Pythonie 3), a nie listę.
  • Działanie zip() przerywa się na najkrótszym iterowalnym.
  • Pozwala na równoległe przetwarzanie kolekcji bez jawnej kontroli indeksów.

Pytania z zacięciem.

Co się stanie, jeśli przekażesz zip() kolekcje o różnej długości?

zip() zatrzyma się, gdy osiągnie koniec najkrótszej kolekcji — pozostałe elementy dłuższych kolekcji zostaną zignorowane.

print(list(zip([1,2,3], ['a','b']))) # [(1, 'a'), (2, 'b')]

Jak uzyskać krotki, uzupełniając krótsze sekwencje wartością domyślną?

Standardowa zip() tego nie obsługuje, ale istnieje itertools.zip_longest dla takiego zachowania:

from itertools import zip_longest for a, b in zip_longest([1,2], ['x','y','z'], fillvalue=None): print(a, b) # 1 x # 2 y # None z

Czy można "rozpakować" wynik zip(), aby ponownie uzyskać pierwotne listy?

Tak, jeśli wszystkie pierwotne kolekcje miały tę samą długość i wynik nie został zmieniony, operator * pozwala rozwinąć zip.

pairs = [(1,2), (3,4)] a, b = zip(*pairs) print(a) # (1, 3) print(b) # (2, 4)

Typowe błędy i antywzorce

  • Oczekiwanie, że zip() zawsze "dojdzie" do końca najdłuższej kolekcji.
  • Przypuszczanie, że w Pythonie 3 zip() zwraca listę (to iterator, czasami trzeba go opakować w list()).
  • Praca z zip na zmiennych źródłach, które są wykorzystywane przy każdej iteracji.

Przykład z życia

Negatywny przypadek

Przetwarzanie powiązanych kolekcji o różnej długości, nie uwzględniając cech zip:

lst1 = [1,2,3,4] lst2 = ['a','b'] for x, y in zip(lst1, lst2): print(x, y) # 1 a # 2 b # (3,4) i 'c', 'd' z lst1 nie zostały przetworzone

Zalety:

  • Proste i zrozumiałe, jeśli sekwencje mają zapewnioną równą długość.

Wady:

  • Utrata wartości, jeśli rzeczywista długość kolekcji się różni.

Pozytywny przypadek

Użycie zip_longest z fillvalue, aby nie zgubić żadnego elementu:

from itertools import zip_longest lst1 = [1,2,3,4] lst2 = ['a','b'] for x, y in zip_longest(lst1, lst2, fillvalue='?'): print(x, y) # 1 a # 2 b # 3 ? # 4 ?

Zalety:

  • Gwarantowane przetwarzanie wszystkich elementów.
  • Można jawnie ustawić wartość "pustą".

Wady:

  • Należy zaimportować zewnętrzny moduł.
  • Ważne jest, aby nie zapomnieć o fillvalue, w przeciwnym razie — None domyślnie.