Dlaczego składanie list (list comprehension) jest uważane za jedną z najlepszych funkcji Pythona?

„Operacje składania list są uważane za jedne z najlepszych funkcji Pythona.”

Python w Analizie Danych – Wes McKinney

Bo pozwalają w sposób zwięzły formować nowe listy (przekonasz się o tym za chwilę). Pisanie kodu jest prostsze a sam kod bardziej elegancki i czytelny.

Składnia może wydawać się trochę zawiła, zwłaszcza dla osób, które pierwszy raz się z tym spotykają, lub przesiadają się z innego języka programowania. Nie ma co się jednak zrażać.

Piękne w składaniu list jest to, że wykorzystanie tego mechanizmu może być proporcjonalne do poziomu zawansowania osoby, która z tego korzysta.

W związku z tym chciałbym Ci zaprezentować 7 Stopni Wtajemniczenia w składanie list.

Stopień 1: Zrozumienie Składni

Pierwszą barierą jaką trzeba pokonać, to „przegryźć” się przez zapis składania list. W składni jest ukryta cała moc i piękno list comprehension.

Podstawowa forma składania list wygląda tak:

my_list = [wyrażenie for wartość in lista (if warunek)]

W jednej linijce mamy 3 elementy:

  • pętle for: for wartość in lista
  • wyrażenie: wykonywane dla każdego elementu
  • opcjonalnie: if warunek

Czyli na dobrą sprawę moglibyśmy zapisać to samo jako pętle:

my_list = []
for wartość in lista:
    if warunek:
        my_list.append(wyrażenie)

Stopień 2: Zastąpienie Pętli For

Najprostszym sposobem wykorzystania składania list jest po prostu zastąpienie pętli for.

Rozważmy sobie taki przykład. Mamy sentencje podzieloną na poszczególne wyrazy.

sentence = 'Czas płynie zbyt szybko. Myśli dręczą zbyt długo. Piękne chwile mijają bezpowrotnie.'
sent_split = sentence.split()
print(sent_split)
['Czas', 'płynie', 'zbyt', 'szybko.', 'Myśli', 'dręczą', 'zbyt', 'długo.', 'Piękne', 'chwile', 'mijają', 'bezpowrotnie.']

Chcielibyśmy pousuwać sobie „.” przy wyrazach „szybko.”, „długo.” i „bezpowrotnie.”.

Możemy zrobić to za pomocą pętli for:

wynik = []
for word in sent_split:
    wynik.append(word.replace('.',''))
print(wynik)
['Czas', 'płynie', 'zbyt', 'szybko', 'Myśli', 'dręczą', 'zbyt', 'długo', 'Piękne', 'chwile', 'mijają', 'bezpowrotnie']

To samo jednak możemy zrobić za pomocą składania list:

wynik = [word.replace('.','') for word in sent_split]
print(wynik)
['Czas', 'płynie', 'zbyt', 'szybko', 'Myśli', 'dręczą', 'zbyt', 'długo', 'Piękne', 'chwile', 'mijają', 'bezpowrotnie']

Stopień 3: Dodanie Warunku If

Warunek if jest opcjonalny. Stosując go możemy sobie odfiltrować część elementów z pierwotnej sekwencji.

Powiedzmy, że z listy wynik(tej co powyżej) chcemy odfiltrować sobie tylko te słowa, których długość jest parzysta.

wynik = [word for word in wynik if len(word) % 2 ==0]
print(wynik)
['Czas', 'płynie', 'zbyt', 'szybko', 'dręczą', 'zbyt', 'Piękne', 'chwile', 'mijają', 'bezpowrotnie']

Pozbyliśmy się słów: ‚Myśli’ i ‚długo’ oba na 5 liter.

Stopień 4: Złożone Wyrażenia

Teraz możemy poprzekształcać sekwencje stosując bardziej skomplikowane wyrażenia.

Przykładowo wszystkie słowa mają być wielką literą. Dodatkowo zamieniamy polskie znaki na angielskie odpowiedniki.

# zamiana polskich znaków na odpowiedniki angielskie https://pypi.org/project/Unidecode/
import unidecode

wynik = [unidecode.unidecode(word).upper() for word in wynik if len(word) % 2 ==0]
print(wynik)
['CZAS', 'PLYNIE', 'ZBYT', 'SZYBKO', 'DRECZA', 'ZBYT', 'PIEKNE', 'CHWILE', 'MIJAJA', 'BEZPOWROTNIE']

Stopień 5: Zagnieżdżone Operacje Składania List

To zagadnienie łatwiej będzie wytłumaczyć na przykładzie.

Punktem wyjścia do zrozumienia mechanizmu zagnieżdżonych operacji składania list mogą być zagnieżdżone pętle for przetwarzające sekwencje.

Powiedzmy, że mamy 3 sentencje łacińskie zebrane w postaci listy list all_sent.

sent1 = 'Ab alio expectes, alteri quod feceris'
sent2 = 'Actus hominis, non dignitas iudicetur'
sent3 = 'Ad quas res aptissimi erimus, in iis potissimum elaborabimus'
all_sent = [sent1.split(),sent2.split(), sent3.split()]
print(all_sent)
[['Ab', 'alio', 'expectes,', 'alteri', 'quod', 'feceris'], 
['Actus', 'hominis,', 'non', 'dignitas', 'iudicetur'], 
['Ad', 'quas', 'res', 'aptissimi', 'erimus,', 'in', 'iis', 'potissimum', 'elaborabimus']]

Zadanie jest takie aby, ze wszystkich trzech list wydobyć łacińskie słowa na „e”.

Za pomocą zagnieżdżonych pętli for, moglibyśmy zrobić to tak:

# lista wszystkich słów łacińskich zaczynających się na e
e_word = []
for sent in all_sent:
    for word in sent:
        if word[:1].lower() == 'e':
            e_word.append(word.replace(',',''))
print(e_word)
['expectes', 'erimus', 'elaborabimus']

To samo za pomocą składania list:

e_word = [word for sent in all_sent for word in sent if word[:1].lower() == 'e' ]
print(e_word)
['expectes', 'erimus', 'elaborabimus']

Porządek tworzenia zagnieżdżonych operacji składania list jest podobny jak w pętlach for. Od pętli for najwyżej w hierarchii for sent in all_sent do tej niżej for word in sent. W powyższym przykładzie z listy list zrobiliśmy jedną płaską listę.

Możliwe jest jednak pozostawienie pierwotnej struktury listy list jak poniżej.

Chcemy wykonać zwiększenie wartości wszystkich elementów 10 razy nie naruszając samej struktury.

lista_list = [[1,2,3],[4,5,6],[7,8,9]]
lista_list = [[item*10 for item in lista ] for lista in lista_list]
print(lista_list)
[[10, 20, 30], [40, 50, 60], [70, 80, 90]]

Stopień 6: Składanie Zbiorów

Składanie zbiorów wygląda podobnie do składania list, ale zamiast nawiasów klamrowych zastosowano nawiasy kwadratowe:

my_set = {wyrażenie for wartość in zbiór (if warunek)}

Załóżmy, że chciałbyś uzyskać zbiór (unikalne_dlugosci) zawierający wartości określające długości poszczególnych łańcuchów wchodzących w skład listy.

unikalne_dlugosci = {len(word) for word in sent_split}
print(unikalne_dlugosci)
{4, 5, 6, 7, 13}

Stopień 7: Składanie Słowników

Operacje składania słowników, podobnie jak operacje składania list, są głównie udogodnieniem, ale mogą też sprawić, że pisanie kodu będzie prostsze, a sam kod będzie bardziej czytelny.

Składanie słowników jest wykonywane następująco:

my_dict= {wyrażenie-klucz : wyrażenie-wartość for wartość in słownik (if warunek)}

Oto przykład składania słowników. W słowniku tym łańcuchom wchodzącym w skład listy przypiszę informacje o ich położeniu. Przy okazji wykorzystamy funkcję enumerate opisaną w tym poście.

mapowanie_pozycji = { index: val.replace('.','').upper() for index,val in enumerate(sent_split)}
print(mapowanie_pozycji)
{0: 'CZAS', 1: 'PŁYNIE', 2: 'ZBYT', 3: 'SZYBKO', 4: 'MYŚLI', 5: 'DRĘCZĄ', 6: 'ZBYT', 7: 'DŁUGO', 8: 'PIĘKNE', 9: 'CHWILE', 10: 'MIJAJĄ', 11: 'BEZPOWROTNIE'}

Podsumowanie

Na pierwszy rzut oka widać piękno składania list. Kod jest bardziej schludny i elegancki. Dodatkowo poziom zawansowania można dostosować do swoich umiejętności, które z czasem urosną.

Największą zaletą składania list jest to że pozwalają się skupić się na samych kolekcjach bardziej niż na sterowaniu przepływem. Zamiast myśleć jak działa pętla for skupiamy się na tym co zawiera nasza sekwencja. A o to chodzi głównie przy pracy z danymi.

Taktyka Killera