Zaczaruj struktury. List / dict / set comprehensions.

Zastanawiałeś/aś się kiedyś, czy można “oszukać” składnię Pythona i pozbyć się nadmiarowych wcięć i enterów?
Jak połączyć tworzenie listy, pętlę for i instrukcję if i niczym Harry Potter 🪄 zamknąć je w jednej magicznej skrzyni… o przepraszam, w jednej (magicznej) linii?

Najwyższy czas poznać sekrety języka węży i zostać czarodziejem Pythona 🐍!

PROBLEM NA DZIŚ

Spójrz na poniższą listę:

numbers = [1, 2, 5, 7, 8, 9, 12, 25, 100]

Jak stworzyć z niej nową listę, zawierającą kwadraty podanych liczb?

Nie wiem jak u Ciebie, ale w moim przypadku pierwsze rozwiązanie, które zwykle wpadało mi do głowy wyglądało nastepująco:

squares = []
for number in numbers:
        squares.append(number ** 2)
print(squares)
# [1, 4, 25, 49, 64, 81, 144, 625, 10000]

Działa? Działa! 😛 Czego chcieć więcej…

Na szczęście w którymś momencie nauki w PyHogwarcie udało mi się awansować do następnej klasy i poznałam magiczną konstrukcję zwaną (list)* comprehensions. W języku polskim nie istnieje (chyba) oficjalna nazwa, ale często możesz spotkać się z dwoma określeniami: wyrażenia listowe lub listy składane. Jako że jestem wybredna i ta druga opcja nie brzmi dobrze w moich uszach, pozwolę sobie posługiwać się zamiennie nazwą angielską i pierwszym z polskich odpowiedników.

* zamiast list możemy użyć też dict lub set, ale o tym nieco później

LIST COMPREHENSIONS (WYRAŻENIA LISTOWE)

Co kryje się pod tą trudną nazwą? Spójrz na poniższy kod:

squares = [number ** 2 for number in numbers]
print(squares)
# [1, 4, 25, 49, 64, 81, 144, 625, 10000]

Hmm, o co tu chodzi? 🤔 Niby podobne do poprzedniego, ale jakoś mniej kodu… Niby pętla for, ale jakaś odwrócona… I jeszcze te nawiasy? 🤨

Spokojnie, już tłumaczę 😁.

Tworzenie nowej listy

🔸Zaczynamy od stworzenia prostej pętli for:

for number in numbers

🔸Z przodu dopisujemy zmodyfikowaną wartość elementu, który chcemy dodawać do naszej listy w każdej iteracji:

number **  2 for number in numbers
Python

🔸Na koniec opakowujemy wszystko w nawiasy kwadratowe i przypisujemy do nowej zmiennej:

squares = [number ** 2 for number in numbers]

No i proszę! Otrzymaliśmy dokładnie ten sam zapis, który pokazałam wyżej. W jednej linii przeszliśmy w pętli po elementach i stworzyliśmy z nich nową listę. Prosta, prawda?

Ale to dopiero początek…

Dodawanie instrukcji warunkowych

Jedna magiczne zaklęcie to zdecydowanie za mało, żeby zostać pełnoprawnym czarodziejem Pythona 🎩. Czas więc poznać kolejną sztuczkę i dodać instrukcję warunkową if.

Załóżmy, że w początkowym przykładzie chcielibyśmy przefiltrować wejściową listę i wyciągnąć z niej tylko liczby parzyste, a później podnieść je do kwadratu.

Wersja z klasyczną pętlą for wyglądałaby tak:

squares = []
for number in numbers:
    if number % 2 == 0:
        squares.append(number**2)

print(squares)
# [4, 64, 144, 10000]

Zapewne domyślasz się już, że musimy skopiować ifa i “włożyć” go między nawiasy…
Tylko gdzie będzie mu najlepiej? 🤔 Z racji, że jest to operacja opcjonalna, dorzućmy ją na koniec!

squares = [number**2 for number in numbers if number % 2 == 0]

print(squares)
# [4, 64, 144, 10000]

⚠️ Jeśli chcemy dodać warunek, według którego będzie tworzona nowa lista, dopisujemy go na końcu wyrażenia.

Czyż to nie piękne 😊?

I tak oto w jednej linii możemy tworzyć listy, modyfikować zwracane w nich wartości i nakładać dodatkowe warunki, do wyboru, do koloru 🤩!

Hmm, właściwie mógłby to już być koniec tego artykułu, ale obiecałam Ci jeszcze kilka magicznych sztuczek (ostrzegam – tylko dla odważnych 😎).

No to zapinamy pasy i lecimy 🧹 (ups, zapomniałam, że nasz magiczny środek transportu nie ma takich zabezpieczeń 🤣, miejmy nadzieję, że jednak uda nam się przeżyć…)

DICT COMPREHENSIONS (WYRAŻENIA SŁOWNIKOWE)

Kolejną pythonową strukturą odmienianą przez wszystkie przypadki jest słownik (jeśli nie wiesz co to, zapraszam Cię tutaj). Twórcy języka węży 🐍 nie mieli więc innej możliwości, jak dostosować konstrukcje comprehensions również do nich. No i nie zawiedli! 🥳

Wróćmy więc do naszej szkoły magii i wykorzystajmy listę znanych czarodziejów.

wizards = ['Harry', 'Hermione', 'Ron']

A teraz masz niepowtarzalną okazja, aby wczuć się w rolę skrupulatnego Dumbledore’a i stworzyć słownik, który każdemu czarodziejowi przyporządkuje ilość liter w jego imieniu (właściwie nie wiem, po co Albusowi takie dane, ale to temat na inną okazję 🤭).

Jak mogłoby wyglądać klasyczne rozwiązanie?

data = {}
for wizard in wizards:
    data[wizard] = len(wizard)

print(data)
# {'Harry': 5, 'Hermione': 8, 'Ron': 3}

Pięknie, 5 w dzienniku jak nic 👍!

Ja jednak zawalczę o wyższą ocenę i wykorzystam wyrażenie dict comprehension. Dołączysz do mnie 🧐?

Tworzenie nowego słownika

Znając już zasady tworzenia wyrażeń listowych, zastanów się chwilę, co należałoby zmodyfikować, żeby dostosować je do słowników…

Masz już pomysł?

No to podaję odpowiedź!

data = {wizard: len(wizard) for wizard in wizards}

print(data)
# {'Harry': 5, 'Hermione': 8, 'Ron': 3}

⚠️ Aby stworzyć wyrażenie słownikowe, wystarczy zamienić nawiasy kwadratowe na klamrowe, a przed pętlą podać pożądany “kształt” elementów słownika.

Pięknie! Dyrektor będzie z Ciebie dumny. I poprosi o jeszcze jedną przysługę…

Zmiana wartości elementów

Mamy do dyspozycji następujący słownik z listami ocen poszczególnych czarodziejów:

wizards_grades = {
    'Harry': [2, 2, 4, 5],
    'Hermione': [5, 5, 5, 5],
    'Ron': [3, 2, 3, 4]
}

Na jego podstawie stwórz podobny słownik, który każdemu uczniowi przyporządkuje średnią jego ocen.

Tym razem nie bawimy się w podpowiedzi i od razu wykorzystamy dict comprehension:

final_grades = {wizard: sum(grades) / len(grades) for wizard, grades in wizards_grades.items()}

print(final_grades)
# {'Harry': 3.25, 'Hermione': 5.0, 'Ron': 3.0}

Na pierwszy rzut oka pojawiło się dużo nowych rzeczy 😟. Ale jeśli przyjrzysz się bliżej, zobaczysz tylko dwie nowości:

🔸jednoczesne pobieranie kluczy i wartości ze słownika: for wizard, grades in wizards_grades.items() (jeśli ten zapis jest dla Ciebie obcy, to koniecznie zajrzyj tutaj),

🔸obliczanie średniej z elementów listy: wizard: sum(grades) / len(grades) .

A to wszystko połączone w jedną całość! Magic in the air! ✨

Dodawanie instrukcji warunkowych

No to czas dołożyć instrukcje warunkowe. Znamy już podstawowe zasady ich tworzenia, więc nie ma na co czekać. Przed nami 3 różne przykłady.

1) Pojedynczy if

Na początek stwórzmy słownik zawierający tylko najlepszych uczniów (ze średnią wyższą niż 4.75).

nerds = {wizard: sum(grades) / len(grades) for wizard, grades in wizards_grades.items() if sum(grades) / len(grades) > 4.75}

print(nerds)
# {'Hermione': 5.0}

Chociaż i bez sprawdzania tego kodu wiadomo było, kto znajdzie się na liście “kujonów” 😅.

No to wrzucamy wyższy bieg.

2) co dwa ify, to nie jeden!

Skoro umiemy już obsłużyć jednego ifa, to co powiesz na dodanie kolejnego?

Tym razem weźmy pod uwagę jeszcze innych uczniów PyHogwartu i obliczone już ich oceny:

final_grades = {
    'Harry': 3.25,
    'Hermione': 5.0,
    'Ron': 3.0,
    'Neville': 4.25,
    'Luna': 4.75,
    'Draco': 3.5
}

Teraz spróbujmy znaleźć tylko tych, których średnia jest większa od 4,0, a ich imię zawiera więcej niż 5 liter. Klasyczny warunek wyglądałby następująco:

if grade > 4 and len(wizard) > 5:

Co się stanie, jeśli skopiujemy go do wyrażenia comprehension?

good_wizards = {
    wizard: grade  for wizard, grade in final_grades.items() if grade > 4 and if len(wizard) > 5
}

Powyższy kod zwróci błąd : SyntaxError: invalid syntax.

Żeby go naprawić, musimy usunąć słowo and. Otrzymamy wówczas taki zapis:

good_wizards = {
    wizard: grade  for wizard, grade in final_grades.items() if grade > 4 if len(wizard) > 5
}
print(good_wizards)
# {'Hermione': 5.0, 'Neville': 4.25}

⚠️ Łącząc kilka instrukcji if wewnątrz comprehensions, nie używamy operatora and!

Zawsze to 3 literki mniej 😁.

No i czas na deser. Ciekawa jestem, czy w Twojej głowie obudziła się już dociekliwa Hermiona i zadała następujące pytanie…

❓ Jeśli możemy bez ograniczeń używac if’ów, czemu nie mielibyśmy dodać else’a?

No właśnie! Nie ograniczajmy się! Skoro w świecie magii wszystko jest możliwe, to w świecie Pythona również.

3) If … Else

Wykorzystajmy więc słownik zawierający końcowe oceny czarodziejów.

final_grades = { 
    'Harry': 3.25, 
    'Hermione': 5.0, 
    'Ron': 3.0
}

I na tej podstawie przypiszmy uczniom dwa statusy. Jeśli średnia ocen przekracza 4.75, nazwiemy go kujonem, w innym przypadku leniwym (wiem, wiem, zaraz ktoś się na mnie oburzy za taką kategoryzację, ale na swoją obronę powiem, że cały czas mówimy o nieistniejącej szkole PyHogwart 😜).

Zastanów się chwilę, jak mógłby wyglądać taki kod, a następnie spójrz na poniższe rozwiązanie.

statuses = { 
    wizard: 'nerd' if grade > 4.75 else 'lazy' for wizard, grade in final_grades.items()
}

print(statuses)
# {'Harry': 'lazy', 'Hermione': 'nerd', 'Ron': 'lazy'}

Ojoj, a co tu się stało? Przecież if miał być na końcu…

Musimy rozróżnić jednak dwa przypadki.

⚠️ If, który filtruje strukturę wejściową umieszczamy na końcu wyrażenia (tak jak w przykładzie z liczbami parzystymi).

⚠️ Z kolei konstrukcję if...else, która zmienia wartości wynikowe umieszczamy na początku wyrażenia.

Trochę pokręcone czary-mary, ale ostrzegałam!

Jak to zapamiętać 🧠?

Pamiętasz dobieranie się w pary w szkole? I klasyczny tekst: “bez pary na koniec”? No to w naszej pythonowej szkole panuje ta sama zasada.

🔸Jeśli if znajdzie swoją parę, czyli else'a, to razem ustawiają się na początku wyrażenia ⬅️.
🔸Jeśli jednak if nie znajdzie koleżanki (ani kolegi), to trafia na koniec wyrażenia ➡️.

SET COMPREHENSIONS

Wszystkie powyższe schematy możemy też wykorzystać przy zbiorach, więc żeby się nie potwarzać zostawię Wam jeden przykład.

numbers = [1, 2, 3, 4, 3, 2, 1]
even = {number for number in numbers if number % 2 == 0}

print(even)
# {2, 4}

A jeśli nie wiesz, czym są zbiory, to już niedługo na naszym blogu artykuł na ten temat! Stay tuned 📣!

NARESZCIE KONIEC

Jeśli to czytasz, to jest mi niezmiernie miło, że dotrwałeś/dotrwałaś aż do końca. Gratuluję wytrwałości! I uratowania jednego tęczowego jednorożca 🦄.

A tak na poważnie, mam nadzieję, że udało mi się pokazać magię wyrażeń listowych i słownikowych. Zachęcam Cię do wykorzystywania tej konstrukcji w praktyce i optymalizowania kodu w jak najlepszy sposób.

Do zobaczenia już niedługo!

P.S. Może masz jakieś ciekawe przykłady wykorzystania list/dict/set comprehensions? Albo chciał(a)byś, żebyśmy poruszyły jakiś konkretny temat? Czekamy na komentarze i wiadomości!

P.S.2 Podziękowania dla Artura B. za tę magiczną inspirację 🤩!

5 1 vote
Article Rating
guest
5 komentarzy
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Bartek
Bartek
7 miesięcy temu

Ciekawa i zabawna forma, czyta się przyjemnie od deski do deski. Gratulacje.

Tomek
Tomek
7 miesięcy temu

Fajnie wytłumaczone w kreatywny sposób, dość rzadko spotykane w naszym ojczystym języku, szanuję.

Boris
7 miesięcy temu

ᴏᴋ