List Comprehension w Pythonie — Kiedy Zamiast Pętli For
Praca i rekrutacja

List Comprehension w Pythonie — Kiedy Zamiast Pętli For

O
Odpytywarka.pl
| | 12 wyświetleń

Po co list comprehension w Pythonie

List comprehension to idiomatyczny sposób tworzenia list w Pythonie — krótszy niż pętla for z append, czytelniejszy w prostych transformacjach. Jest jednym z pierwszych testowanych konceptów na rozmowach kwalifikacyjnych i pojawia się w niemal każdym code review.

Ten artykuł przechodzi przez składnię, kiedy używać, kiedy NIE używać, generator expression, set/dict comprehension, walrus operator, flatten oraz typowe pułapki. Każdy snippet ma # expected_output, więc widzisz dokładnie co Python zwróci — bez konieczności uruchamiania.

Wszystkie przykłady zakładają Python 3.12+. Walrus operator wymaga Python 3.8+. Identifiers w kodzie są po angielsku.

Co to jest list comprehension

List comprehension to składnia tworzenia nowej listy z iterable: [expr for item in iterable]. Część expr opisuje wartość trafiającą do wyniku, a for mówi skąd biorą się elementy.

numbers = [1, 2, 3, 4]
squares = [number * number for number in numbers]

print(squares)  # [1, 4, 9, 16]

Składnia opisana w PEP 202. Najczęściej służy do transformacji danych: zmiany typu, wyliczenia nowej wartości albo wyciągnięcia pola z obiektu.

Pułapka rekrutacyjna: list comprehension tworzy całą listę od razu. Przy bardzo dużych danych może zużyć dużo pamięci, nawet jeśli potrzebujesz tylko kilku pierwszych wyników.

List comp vs pętla for — kiedy która

List comprehension jest lepszy gdy celem jest zbudowanie nowej listy przez prostą transformację lub filtr. Pętla for jest lepsza, gdy logika ma wiele kroków, obsługę błędów albo side effects (logowanie, wywołania API, modyfikacja stanu).

names = ["ada", "grace", "guido"]
capitalized = [name.title() for name in names]

print(capitalized)  # ['Ada', 'Grace', 'Guido']

Odpowiednik z pętlą for jest dłuższy o 2-3 linie, ale czasem bardziej czytelny. Jeśli trzeba dodać kilka warunków, walidację lub aktualizować kilka struktur, zwykła pętla zwykle wygrywa.

Pułapka rekrutacyjna: nie używaj comprehension tylko dlatego, że jest krótszy. Readability > cleverness, szczególnie gdy wyrażenie robi więcej niż jedną prostą rzecz.

List comprehension z filtrem (if)

Filtr dodaje się po części for: [expr for item in iterable if condition]. Warunek decyduje które elementy przechodzą do wyniku, a expr nadal może przekształcać każdy zaakceptowany element.

numbers = [1, 2, 3, 4, 5, 6]
even_squares = [number * number for number in numbers if number % 2 == 0]

print(even_squares)  # [4, 16, 36]

Kolejność czytania przypomina pętlę: weź number z numbers, sprawdź if, dopiero potem policz wynik. To ułatwia porównanie comprehension z klasycznym kodem imperatywnym.

Pułapka rekrutacyjna: filtr if w comprehension nie ma części else. Jeśli chcesz wybrać jedną z dwóch wartości, użyj conditional expression w expr: a if condition else b.

Złożoność i wydajność list comprehension

Dla jednego przejścia po iterable list comprehension i pętla for mają zwykle tę samą złożoność czasową O(n). Złożoność pamięciowa wyniku też O(n) — powstaje nowa lista.

numbers = range(5)
values = [number + 10 for number in numbers]

print(values)  # [10, 11, 12, 13, 14]

W praktyce list comprehension bywa szybszy od ręcznej pętli z append w prostych przypadkach, ale różnica zależy od kodu, danych i wersji Pythona. Nie zmienia to klasy złożoności — przy wąskim gardle zawsze mierz w realnym kodzie.

Pułapka rekrutacyjna: nie optymalizuj kosztem czytelności bez pomiaru. Różnica między O(n) a O(n) nie istnieje teoretycznie, a praktyczne różnice są małe wobec drogich operacji wewnątrz wyrażenia.

List comp vs map/filter

map() i filter() zwracają iteratory (lazy), a list comprehension od razu zwraca listę (eager). Gdy potrzebujesz listy i logika jest krótka, comprehension jest zwykle bardziej idiomatyczny w Pythonie.

words = ["python", "sql", "api"]
lengths = [len(word) for word in words]

print(lengths)  # [6, 3, 3]

map() dobrze pasuje gdy masz już nazwaną funkcję (nie lambda) i chcesz zastosować ją bez dodatkowej logiki. filter() bywa mniej czytelny z lambda, szczególnie gdy warunek jest prosty.

Pułapka rekrutacyjna: map() i filter()lazy — samo ich utworzenie nie wykonuje pracy. Jeśli porównujesz z list comprehension, porównuj też moment materializacji (np. przez list()).

Generator expression — leniwa wersja

Generator expression wygląda podobnie, ale używa nawiasów okrągłych: (expr for item in iterable). Jest lazy — liczy kolejne wartości dopiero podczas iteracji, zamiast tworzyć całą listę naraz.

numbers = [1, 2, 3, 4]
total = sum(number * number for number in numbers)

print(total)  # 30

Generator jest dobry dla dużych danych, pipeline'ów i funkcji takich jak sum, any, all, min, max. List comprehension jest lepszy gdy wynik ma być indeksowany, wielokrotnie używany albo modyfikowany.

Pułapka rekrutacyjna: generator jest single-pass i nie ma len(). Po przejściu przez wszystkie elementy jest zużyty — ponowna iteracja zwróci pustą sekwencję.

Set i dict comprehension

Set comprehension używa {expr for item in iterable} i automatycznie usuwa duplikaty. Dict comprehension (PEP 274) używa {key_expr: value_expr for item in iterable} i tworzy słownik.

words = ["api", "python", "api", "sql"]
unique_lengths = {len(word) for word in words}
index_by_word = {word: len(word) for word in words}

print(sorted(unique_lengths))  # [3, 6]
print(index_by_word)            # {'api': 3, 'python': 6, 'sql': 3}

Składnia jest analogiczna do list comprehension, ale typ wyniku zależy od nawiasów i obecności dwukropka. {x for x in data} tworzy set, {k: v for ...} tworzy dict.

Pułapka rekrutacyjna: przy dict comprehension powtarzające się klucze nadpisują wcześniejsze wartości. To poprawne zachowanie słownika, ale może ukryć utratę danych jeśli klucze nie są unikalne.

Nested comprehension i flatten

Nested comprehension może oznaczać dwa różne wzorce: zagnieżdżoną listę jako wynik albo kilka klauzul for w jednej comprehension. Do flatten najczęściej używa się kolejnych for w tej samej kolejności co pętle.

rows = [[1, 2], [3, 4], [5, 6]]
flattened = [value for row in rows for value in row]

print(flattened)  # [1, 2, 3, 4, 5, 6]

Dla transpose dokumentacja Pythona pokazuje nested list comprehension, ale zip(*matrix) jest często czytelniejszy — bezpośrednio komunikuje operację zamiany wierszy z kolumnami.

matrix = [[1, 2, 3], [4, 5, 6]]
transposed = [list(column) for column in zip(*matrix)]

print(transposed)  # [[1, 4], [2, 5], [3, 6]]

Pułapka rekrutacyjna: kolejność for w flatten bywa myląca. Czytaj od lewej do prawej jak pętle zagnieżdżone: najpierw for row in rows, potem for value in row.

Walrus operator w comprehension

Walrus operator := (PEP 572, Python 3.8+) pozwala przypisać wartość wewnątrz wyrażenia. W comprehension bywa użyteczny, gdy chcesz obliczyć coś raz i użyć w filtrze lub wyniku.

words = ["a", "python", "go", "typescript"]
long_lengths = [length for word in words if (length := len(word)) > 3]

print(long_lengths)  # [6, 10]

Ten zapis ogranicza powtarzanie kosztownych obliczeń. Sensowny gdy nazwa pośrednia naprawdę upraszcza kod, a nie tylko skraca jedną linijkę kosztem zrozumiałości.

Pułapka rekrutacyjna: zmienna pętli comprehension w Pythonie 3 NIE wycieka poza comprehension. Ale walrus ma inne reguły — przypisana wartość może pozostać widoczna w otaczającym scope.

Common patterns z comprehension

Najczęstsze wzorce to transform, filter, flatten, transpose i przygotowanie danych przed agregacją. Comprehension dobrze nadaje się do normalizacji tekstu — jedna linia oddziela etap czyszczenia od dalszej logiki (np. zliczania przez Counter).

from collections import Counter

raw_words = [" Python ", "python", "SQL"]
normalized = [word.strip().lower() for word in raw_words]
counts = Counter(normalized)

print(counts)  # Counter({'python': 2, 'sql': 1})

Pułapka rekrutacyjna: side effects w comprehension to anti-pattern. Jeśli celem jest print, zapis do pliku, mutowanie obiektów albo wywołanie API — użyj zwykłej pętli for.

Sygnały rozpoznawcze: kiedy list comp, kiedy for, kiedy generator

Sygnał → wybór:

"prosta transformacja jednego         → list comprehension
 iterable + opcjonalny filtr"
"pipeline transformacji,              → generator expression (...)
 streaming, n duże"
"wieloliniowa logika, walidacja,      → for loop
 side effects, debugging"
"liczenie wystąpień / frequency"      → Counter (dict comp też zadziała)
                                        ale Counter szybsze
"unikalne wartości"                   → set comprehension {expr for ...}
"key-value mapping"                   → dict comprehension {k: v for ...}
"flatten zagnieżdżonej listy"         → [x for row in rows for x in row]
                                        (dwa for, lewy = zewnętrzny)

Sygnały odwrotne — kiedy NIE comprehension: if/else jako filtr (comp ma if cond else b w expr, nie po for), side effects (zapis do bazy w pętli), wieloliniowa logika z try/except, debugowanie krok po kroku.

Najczęstsze błędy

Comprehension dla wszystkiego. Czytelność > zwięzłość — gdy logika jest wieloliniowa lub wymaga try/except, pętla for wygrywa. Mechanizm błędu: kandydat wciska 4 zagnieżdżone for z dwoma if w jedną linię — kod jest skondensowany ale nieczytelny. Naprawa: jeśli czytasz comprehension dłużej niż 10 sekund, przepisz na pętlę.

List comp tworzy CAŁĄ listę w pamięci. Dla n=10⁶ plus transformacja może oznaczać setki MB RAM. Mechanizm błędu: [transform(x) for x in huge_iter] materializuje wszystko na raz. Naprawa: generator expression (transform(x) for x in huge_iter) — leniwy, single-pass, O(1) pamięci.

Filtr if nie ma części else. Próba [x if x > 0 for x in nums] rzuca SyntaxError. Mechanizm: if po for to filtr (decyduje czy element przechodzi), if/else przed for to conditional expression na wartości (decyduje co zwrócić). Naprawa: filtruj — [x for x in nums if x > 0]; transformuj z if/else[x if x > 0 else 0 for x in nums].

{} to pusty dict, nie pusty set. Klamry {1, 2} z elementami to set, ale puste {} historycznie oznaczają dict. Mechanizm błędu: kandydat pisze seen = {} jako pusty set, potem seen.add(x) rzuca AttributeError. Naprawa: seen = set() dla pustego setu.

Dict comp z duplikatami kluczy nadpisuje cicho. {x % 3: x for x in [1, 4, 7]} daje {1: 7} — wartości dla x=1 i x=4 zostają nadpisane przez x=7. Mechanizm: dict ma unikalne klucze, ostatnie wstawienie wygrywa. Naprawa: jeśli chcesz grupować, użyj defaultdict(list) z for (szczegóły Słowniki i Sety).

Najczęściej zadawane pytania

Czym różni się list comprehension od pętli for?

Składniowo: jedna linia [expr for item in iterable] zamiast 3-4 linii pętli z append. Funkcjonalnie: comprehension jest expression (zwraca wartość), a pętla to statement. List comprehension lepszy do prostych transformacji; pętla for lepsza gdy masz wiele kroków, side effects, walidację.

Kiedy list comprehension jest szybszy od pętli for?

W prostych przypadkach comprehension bywa szybszy od ręcznej pętli z append (bytecode ma optymalizację dla tego wzorca). Różnica zależy od kodu, danych i wersji Pythona. Klasa złożoności jest taka sama (O(n)) — różnice są małe wobec kosztów wewnątrz wyrażenia.

Jak dodać warunek if w list comprehension?

Filtr if umieszczasz po for: [expr for item in iterable if condition]. Filtr decyduje które elementy przechodzą. Filtr if nie ma części else — jeśli chcesz wybór wartości, użyj conditional expression w expr: [a if cond else b for item in iterable].

Czy generator expression to to samo co list comprehension?

Nie. Składnia podobna, ale nawiasy okrągłe (expr for ...) zamiast kwadratowych. Generator jest lazy (liczy podczas iteracji), list comprehension jest eager (tworzy listę od razu). Generator nie ma len() i jest single-pass — po wyczerpaniu nie iterujesz drugi raz.

Jak spłaszczyć listę zagnieżdżoną przez comprehension?

Użyj dwóch klauzul for: [value for row in rows for value in row]. Czytaj od lewej do prawej jak zagnieżdżone pętle: najpierw zewnętrzna for row, potem wewnętrzna for value. Kolejność for jest taka sama jak w równoważnej pętli.

Co robi walrus operator := w comprehension?

Walrus := (Python 3.8+) przypisuje wartość wewnątrz wyrażenia. W comprehension pozwala obliczyć coś raz i użyć w filtrze lub wyniku, np. [length for word in words if (length := len(word)) > 3]. Sensowny gdy nazwa pośrednia upraszcza czytelność.

Powiązane tematy

Sprawdź swoją wiedzę quizem

Najlepszy sposób na utrwalenie wiedzy z list comprehension to przerobienie jej w formie quizu. Wygenerowaliśmy quiz na bazie tego artykułu — odpowiedz na 15 pytań i sprawdź gdzie masz luki przed rozmową.

Rozwiąż quiz: List comprehension w Pythonie

Możesz też wgrać własny PDF z notatkami (do 20 MB) na odpytywarka.pl — w 30-60 sekund wygenerujemy quiz z 15 pytaniami jednokrotnego wyboru z pierwszych 3 stron dokumentu, za darmo i bez rejestracji do rozwiązania.

Źródła

O

Odpytywarka.pl

Platforma do automatycznego generowania quizów z materiałów edukacyjnych. Artykuły oparte na badaniach z zakresu psychologii uczenia się i edukacji.

Więcej o platformie

Powiązane artykuły

Stwórz własny quiz

Zamień swoje notatki, podręczniki lub dowolny PDF w interaktywny quiz. Nauka jeszcze nigdy nie była tak prosta!