Cześć,
dziś wezmę na tapet trzy elementy języka Python:
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!
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!
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.
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.
Oj daaawnoo mnie tu nie było. Ale wakacje to był czas dużej liczby intensywnych wyjazdów i tak naprawdę, dopiero jakoś… Read More
Cześć! Zapraszam na krótkie podsumowanie kwietnia. Wyjazd do Niemiec A dokładniej pod granicę z Francją. Chrześnica miała pierwszą komunię. Po… Read More
Ostatnio tygodnie były tak bardzo wypełnione, że nie udało mi się napisać nawet krótkiego podsumowanie. Więc dziś zbiorczo podsumuję luty… Read More
Zapraszam na krótkie podsumowanie miesiąca. Książki W styczniu przeczytałem "Homo Deus: Historia jutra". Książka łudząco podoba do wcześniejszej książki tego… Read More
Cześć! Zapraszam na podsumowanie roku 2023. Książki Zacznijmy od książek. W tym roku cel 35 książek nie został osiągnięty. Niemniej… Read More
Zapraszam na krótkie podsumowanie miesiąca. Książki W grudniu skończyłem czytać Mein Kampf. Nudna książka. Ciekawsze fragmenty można by było streścić… Read More
Pokaż komentarze
super wytłumaczone, robisz świetną robotę, dziękuję
To ja dziękuję za taki komentarz! Od razu się chce bardziej:)
Ja najczęściej korzystam z .map :)
Każdy ma jakieś podejście które sobie wyrobił na przestrzeni czasu:) i myślę że to jest spoko:)
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 :-).
Cóż za zwięzłość ekspresji, czuję że będę tu zaglądał.
Wiadomo czemu tak zachowują się polecenia? Czemu w przypadku pierwszym jest az taka różnica np.
Nie podglądałem bytecode'u, a pewnie tam znajduje się odpowiedź :D możliwe że kiedyś podejrzę i zrobię osobny wpis. Jeśli mnie uprzedzisz, to będę super wdzięczny, jeśli podzieliłbyś się wnioskami w komentarzy, znacząco rozszerzyło by to zakres artykułu:)