Mateusz Mazurek – programista z pasją

Python, architektura, ciekawostki ze świata IT

Programowanie Programowanie webowe

For vs list comprehension vs map

Cześć,

dziś wezmę na tapet trzy elementy języka Python:

  • pętlę for,
  • list comprehension,
  • funkcję map.

Pomierzymy sobie ich efektywność w dwóch sytuacjach i na koniec wyciągniemy wnioski, odpowiadając na pytanie: co jest szybsze: pętla for, list comprehension czy funkcja map? Zapraszam!

Sytuacja pierwsza: wynik jest nam potrzebny

Wyobraźmy sobie, że działamy pętlą po jakiejś kolekcji, na jej elementach wykonujemy funkcję i wynik tej funkcji potrzebujemy zachować. Może to być np. wygenerowanie potęg liczb od 0 do 99. Możemy to zrobić na trzy sposoby:

Za pomocą standardowej pętli for:

1
2
3
numbers = []
for x in range(100):
    numbers.append(x * x)

Następnie z użyciem starej, dobrej listy składanej:

1
numbers = [x * x for x in range(100)]

I na koniec – funkcji map:

1
numbers = list(map(lambda x: x * x, range(100)))

Aby była pewność, że każde z tych podejść da ten sam efekt, dorzucam kawałek kodu, który to potwierdza:

1
2
3
4
5
6
7
8
9
numbers = []
for x in range(100):
    numbers.append(x * x)
   
numbers2 = [x * x for x in range(100)]

numbers3 = list(map(lambda x: x * x, range(100)))

print(numbers == numbers2 == numbers3)

Jego rezultatem jest oczywiście „True”.

Jeżeli ten sam rezultat można uzyskać na trzy różne sposoby, to który wybrać? Zmierzmy to!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from timeit import timeit

code1 = """
numbers = []
for x in range(100):
    numbers.append(x * x)
"""


code2 = """
numbers = [x * x for x in range(100)]
"""


code3 = """
numbers = list(map(lambda x: x * x, range(100)))
"""



print("For loop:", timeit(code1, number=100000))
print("List comp:", timeit(code2, number=100000))
print("Funkcja map:", timeit(code3, number=100000))

Wynik pomiaru jest dość ciekawy:

For loop: 1.0205242090160027
List comp: 0.6673786389874294
Funkcja map: 1.0319141389336437

Tę kategorię wygrywa, przeciwników zostawiając w tyle, podejście z użyciem list comprehensions!


Czekaj, stop!

Podoba Ci się to co tworzę? Jeśli tak to zapraszam Cię do zapisania się na newsletter:
a w ramach prezentu otrzymasz całkowicie za darmo, dwa dokumenty PDF „6 (nie zawsze oczywistych) błędów popełnianych podczas nauki programowania” który jest jednym z efektów ponad siedmioletniej pracy i obserwacji rozwoju niejednego programisty oraz „Wstęp do testowania w Pythonie”, będący wprowadzeniem do biblioteki PyTest.
Jeśli to Cię interesuje to zapraszam również na swoje social media.

Jak i do ewentualnego postawienia mi kawy :)
Postaw mi kawę na buycoffee.to

Sytuacja druga: wynik nie jest nam potrzebny

Nasze podejście trochę się zmienia, gdy zmienia się sytuacja w której używamy tych elementów. W poprzednim przykładzie wynik przetwarzania miał być nam potrzebny w dalszej części programu. Teraz zakładamy, że nie jest on mam potrzebny. Mamy znów trzy podejścia. Kolejno z pętlą for, listą składaną oraz funkcją map:

1
2
for x in numbers:
    mock(x)
1
[mock(x) for x in numbers]
1
list(map(mock, numbers))

Tutaj słowo wyjaśnienia – funkcja map tworzy generator i nie uruchomi naszej funkcji, dopóki nie będzie ku temu okazji. Okazję wymuszamy konwersją na listę.

Skoro mamy zebrane rzeczy do analizy, to tak jak wcześniej, zmierzmy to!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from timeit import timeit


setup = """
def mock(x):
    return x * x

numbers = range(100)
"""


code1 = """
for x in numbers:
    mock(x)
"""


code2 = """
[mock(x) for x in numbers]
"""


code3 = """
list(map(mock, numbers))
"""


print("For loop:", timeit(code1, number=100000, setup=setup))
print("List comp:", timeit(code2, number=100000, setup=setup))
print("Funkcja map:",timeit(code3, number=100000, setup=setup))

Rezultaty pomiaru:

For loop: 0.9080528659978881
List comp: 1.028726649004966
Funkcja map: 0.8416485550114885

Tutaj nie ma już jednogłośnego zwycięzcy. Niby funkcja map wygrywa, ale jest tylko troszkę szybsza niż pętla for.

Podsumownie

Wnioski nasuwają się same, prawda? Jeśli potrzebujesz pracować na danych w dalszej części programu, to używaj listy składanej. Jeśli nie ma potrzeby pracy na wyniku przetwarzania, a samo uruchomienie funkcji jest wystarczające to… To tu nie jest tak jednoznacznie. To, co bym polecał, to jednak używać pętli for. Różnica jest niewielka, a czytelność znacznie lepsza.

Oczywiście nie wszystkie pętle jesteśmy w stanie zapisać z użyciem list składanych czy zapakować do funkcji map. Przykładowo, jeśli Twoja pętla posiada słówka break lub continue, to może być ciężko.

Dzięki za wizytę,
Mateusz Mazurek

A może wolisz nowości na mail?

Subskrybuj
Powiadom o
guest

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.

8 komentarzy
Inline Feedbacks
View all comments
Krzysztof

super wytłumaczone, robisz świetną robotę, dziękuję

Łukasz

Ja najczęściej korzystam z .map :)

Mateusz H.

Bardzo fajny artykuł.

Dla mnie map było mało zrozumiałe do czasu aż z JS przerzuciłem się na Pythona. W JS klepałem najczęściej pętle for do wszystkiego. W starszej wersji JS map chyba nie było, natomiast w nowszej wersji JS funkcja map już doszła oraz pojawiło się też bardzo dużo nowych elementów.

Ten artykuł trochę mi przypomina artykuł na moim blogu o optymalizacji kodu w Pythonie.

Pozdrawiam :-).

Miro

Cóż za zwięzłość ekspresji, czuję że będę tu zaglądał.

Radek

Wiadomo czemu tak zachowują się polecenia? Czemu w przypadku pierwszym jest az taka różnica np.