programowanieProgramista Backend

Opisz mechanizm działania wyrażeń listowych (list comprehensions) w Pythonie. Czym różni się list comprehension od funkcji map() i pętli for, jakie są zalety i wady każdego podejścia oraz jakie błędy mogą wystąpić przy nieumiejętnym zastosowaniu?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Wyrażenia listowe (list comprehensions) to zwięzły sposób tworzenia list na podstawie istniejących obiektów iterowalnych za pomocą krótkiej składni:

squares = [x**2 for x in range(10)]

Ta zapis jest równoważny:

squares = [] for x in range(10): squares.append(x**2)

Wyrażenia listowe mają kilka zalet:

  • Zwięzłość i czytelność (szczególnie dla prostych transformacji);
  • Możliwość włączania warunków (filter): evens = [x for x in range(10) if x % 2 == 0];
  • Wyrażenia zwracają listę od razu, ich wynik można wykorzystać dalej.

Odpowiednik z map():

def f(x): return x**2 squares = list(map(f, range(10)))

map jest szybszy na dużych danych, jeśli używana jest funkcja już istniejąca w C, i nadaje się do zastosowania jednej funkcji na wszystkich elementach. List comprehension — do wszelkich wyrażeń, a nie tylko gotowych funkcji. Pętla for jest bardziej elastyczna, ale bardziej rozbudowana.


Pytanie z podstępem.

Dlaczego w wyrażeniu typu [x for x in range(10)] zmienna x po wykonaniu listy okazuje się dostępna poza wyrażeniem w Pythonie 2, ale nie w Pythonie 3?

Odpowiedź: W Pythonie 2 zmienna pętli (x) zachowuje swoją wartość po wykonaniu list comprehension. W Pythonie 3 — jest "izolowana" i poza listą niedostępna, co zapobiega niepożądanym efektom ubocznym.

Przykład:

# Python 2.x: [x for x in range(3)] print(x) # x == 2 # Python 3.x: [x for x in range(3)] print(x) # NameError: name 'x' is not defined

Przykłady rzeczywistych błędów z braku znajomości niuansów tematu.


Historia 1

Jeden programista w dużym projekcie chciał filtrować i tworzyć nową listę za pomocą list comprehension:

my_list = [item.transform() for item in data if item.is_valid()]

Jednak operacja item.transform() wywoływała błąd, jeśli item.is_valid() zwróciło False. Funkcja sprawdzająca była napisana z potencjalnym efektem ubocznym, a w efekcie list comprehension nieoczywiście psuł części kodu z efektami ubocznymi.


Historia 2

W projekcie podczas migracji z Pythona 2 do Pythona 3 programista był pewny, że zmienna pętli pozostanie dostępna:

[x for x in range(5)] print(x) # Oczekiwał, że dostanie 4, ale otrzymał NameError.

To spowodowało błąd w logice cyklicznej, gdzie zmienna miała pozostawać będąca użyteczna poza comprehension.


Historia 3

Użycie zagnieżdżonych wyrażeń listowych bez wyraźnego wskazania poziomów:

def flatten(matrix): return [cell for row in matrix for cell in row]

Nowicjusze często napotykają błędy z powodu błędnej kolejności przeszukiwania (np. [cell for cell in row for row in matrix] lub zbędnego zagnieżdżenia), co prowadzi do błędnego wyniku — listy jednowymiarowej zamiast dwuwymiarowej i odwrotnie.