Zadanie

Czy poniższy kod wykona się bez błędów?

lista = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

kolekcja_pierwsza = [x for x in lista if x % 2 == 0]
kolekcja_druga = filter(lambda x: x % 2 == 0, lista)

for x, y in zip(kolekcja_pierwsza, kolekcja_druga):
    assert x == y

Co robi (albo co ma robić) nasz kod

Naszym zadaniem jest  wybranie z listy wartości podzielnych przez 2. Można to zrobić na wiele sposobów. Najprostszym rozwiązaniem jest użycie zwykłej pythonowej pętli.

nowa_lista = []
for x in lista:
    if x % 2 == 0:
        nowa_lista.append(x)

print(nowa_lista)

Output:

[2, 4, 6, 8, 10]

Takie podejście jest jak najbardziej poprawne. Wydaje się też dość intuicyjne i pewnie dlatego w ten sposób postępuje wielu początkujących programistów. Efektem końcowym naszego programu jest nowa lista zawierająca elementy podzielne przez 2. Czyli wszytko działa. Jednak można to zrobić lepiej.

Lista składana (list comprehension)

Spróbujmy teraz przerobić naszą pętlę na listę składaną. Oto jak poszczególne elementy naszej pętli mapują się na elementy składni listy składnej.

Zauważ, że listę składaną możemy zapisać zarówno w jednej jak i w wielu liniach. Także zapis:

nowa_lista = [x for x in lista if x % 2 == 0]

będzie równoważny:

nowa_lista = [
    x
    for x in lista
    if x % 2 == 0
]

Dla mnie opcja zapisu w wielu liniach jest o wiele czytelniejsza.

Dlaczego warto stosować listy składne

  1. Zwiększamy czytelność naszego kodu
  2. Redukujemy ilość niepotrzebnych linii
  3. Listy składne są szybkie
  4. Listy składne są bardziej efektywne
  5. Jesteśmy bardziej Pythonic 🙂

Funkcja filter

Python oferuje wiele funkcji wbudowanych. Jedną z nich jest funkcja filter. Przyjmuje ona dwa parametry filter(function, iterable).

  • functionfunkcja która będzie wykonana dla każdego elementu z kolekcji przekazanej w drugim argumencie. Jeśli przekazana funkcja dla konkretnego argumentu zwróci True oznacza to, że ten konkretny element znajdzie się w „odfiltrowanej” liście wynikowej.
  • iterablekolekcja którą poddamy filtrowaniu

Jak użyć filter ?

Znowu zastanówmy się jak przerobić naszą zwykłą pętlę tym razem na funkcję filter:

Zobaczmy te dwie struktury w akcji

lista = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

kolekcja_pierwsza = [x for x in lista if x % 2 == 0]
kolekcja_druga = filter(lambda x: x % 2 == 0, lista)

for x, y in zip(kolekcja_pierwsza, kolekcja_druga):
    print(f'x = {x}  y = {y}')

Output:

x = 2  y = 2
x = 4  y = 4
x = 6  y = 6
x = 8  y = 8
x = 10  y = 10

Jak widać obie kolekcje wyświetliły te same wartości. Zatem wiemy już, że nasza asercja nie powinna powodować żadnych problemów. Czy to znaczy, że kolekcje są sobie równoważne ? No nie do końca. Lista składna zwraca nowy obiekt typu list. Umożliwia to nam iterowanie po naszej nowej kolekcji. Inaczej wygląda sprawa z funkcją filter. Zwraca ona obiekt typu filter. Również można się po nim iterować jednak troszkę inaczej niż w przypadku listy. Nie zrozumcie mnie źle – z zewnątrz wygląda to tak samo co dowiedliśmy używając funkcji zip. Jednak pod spodem działa to zupełnie inaczej. Iterowanie się po kolekcji typu filter jest leniwe. Co to oznacza i jakie są tego plusy i minusy dowiemy się w kolejnym zadaniu. 🙂