Czy istnieje powód, aby preferować używanie zamiast listowego rozumowania map()
lub odwrotnie? Czy któryś z nich jest na ogół bardziej wydajny lub uważany za bardziej pytoniczny niż drugi?
Czy istnieje powód, aby preferować używanie zamiast listowego rozumowania map()
lub odwrotnie? Czy któryś z nich jest na ogół bardziej wydajny lub uważany za bardziej pytoniczny niż drugi?
Odpowiedzi:
map
może być mikroskopijnie szybszy w niektórych przypadkach (kiedy NIE robisz lambdy do tego celu, ale używasz tej samej funkcji w mapach i listcomp). Zrozumienie listy może być szybsze w innych przypadkach i większość (nie wszystkie) pythonistów uważa je za bardziej bezpośrednie i jaśniejsze.
Przykład małej przewagi mapy przy korzystaniu z dokładnie tej samej funkcji:
$ python -mtimeit -s'xs=range(10)' 'map(hex, xs)'
100000 loops, best of 3: 4.86 usec per loop
$ python -mtimeit -s'xs=range(10)' '[hex(x) for x in xs]'
100000 loops, best of 3: 5.58 usec per loop
Przykład całkowitego odwrócenia porównania wydajności, gdy mapa potrzebuje lambda:
$ python -mtimeit -s'xs=range(10)' 'map(lambda x: x+2, xs)'
100000 loops, best of 3: 4.24 usec per loop
$ python -mtimeit -s'xs=range(10)' '[x+2 for x in xs]'
100000 loops, best of 3: 2.32 usec per loop
map(operator.attrgetter('foo'), objs)
łatwiejsze do odczytania niż [o.foo for o in objs]
?!
o
tutaj, a twoje przykłady pokazują, dlaczego.
str()
przykładzie.
Skrzynie
map
, chociaż uważa się ją za „niepythonic”. Na przykład map(sum, myLists)
jest bardziej elegancki / zwięzły niż [sum(x) for x in myLists]
. Zyskasz elegancję nie konieczności uzupełnić zmienną (np obojętne sum(x) for x...
lub sum(_) for _...
lub sum(readableName) for readableName...
), który trzeba wpisać dwa razy, po prostu iteracyjne. Ten sam argument odnosi się do filter
a reduce
i coś z itertools
modułem: jeśli masz już funkcja przydatna, można iść do przodu i robić niektóre programowania funkcjonalnego. Zyskuje to czytelność w niektórych sytuacjach, a traci ją w innych (np. Początkujący programiści, wiele argumentów) ... ale czytelność twojego kodu w dużej mierze zależy od twoich komentarzy.map
funkcji jako funkcji abstrakcyjnej podczas programowania funkcjonalnego, w którym map
mapujesz lub curry map
, lub w inny sposób skorzystaj z rozmowy map
jako funkcji. Na przykład w Haskell interfejs funktora o nazwie fmap
uogólnia odwzorowanie na dowolną strukturę danych. Jest to bardzo rzadkie w Pythonie, ponieważ gramatyka pytona zmusza cię do używania stylu generatora do mówienia o iteracji; nie możesz tego łatwo uogólnić. (Czasami jest to dobre, a czasem złe.) Prawdopodobnie możesz wymyślić rzadkie przykłady python, gdzie map(f, *lists)
jest to rozsądne. Najbliższym przykładem, jaki mogę wymyślić sumEach = partial(map,sum)
, jest jeden-liniowiec, który w przybliżeniu odpowiada:def sumEach(myLists):
return [sum(_) for _ in myLists]
for
-loop : Można też oczywiście po prostu użyć pętli for. Choć nie są tak eleganckie z punktu widzenia programowania funkcjonalnego, czasami zmienne nielokalne sprawiają, że kod jest wyraźniejszy w imperatywnych językach programowania, takich jak python, ponieważ ludzie są bardzo przyzwyczajeni do czytania kodu w ten sposób. Pętle For są również na ogół najbardziej wydajne, gdy wykonujesz po prostu jakąkolwiek złożoną operację, która nie buduje listy, takiej jak listy i mapy są zoptymalizowane (np. Sumowanie lub tworzenie drzewa itp.) - przynajmniej wydajna pod względem pamięci (niekoniecznie pod względem czasu, gdzie w najgorszym wypadku spodziewałbym się stałego czynnika, z wyjątkiem rzadkich patologicznych czkawek przy zbieraniu śmieci).„Pythonizm”
Nie podoba mi się słowo „python”, ponieważ nie uważam, że python jest zawsze elegancki w moich oczach. Mimo to, map
a filter
i podobne funkcje (takie jak bardzo przydatnym itertools
modułem) są prawdopodobnie uważane unpythonic pod względem stylu.
Lenistwo
Pod względem wydajności, podobnie jak większość funkcjonalnych konstruktów programistycznych, MAP CAN BE LAZY , aw rzeczywistości jest leniwy w Pythonie. Oznacza to, że możesz to zrobić (w python3 ), a komputerowi nie zabraknie pamięci i nie stracisz wszystkich niezapisanych danych:
>>> map(str, range(10**100))
<map object at 0x2201d50>
Spróbuj to zrobić ze zrozumieniem listy:
>>> [str(n) for n in range(10**100)]
# DO NOT TRY THIS AT HOME OR YOU WILL BE SAD #
Zauważ, że rozumienie list jest z natury leniwe, ale Python zdecydował się je zaimplementować jako nieleniwe . Niemniej jednak Python obsługuje wyrażenia leniwych list w postaci wyrażeń generatora, jak następuje:
>>> (str(n) for n in range(10**100))
<generator object <genexpr> at 0xacbdef>
Zasadniczo można myśleć o [...]
składni jako przekazywaniu wyrażenia generatora do konstruktora listy, takiego jak list(x for x in range(5))
.
Krótki wymyślony przykład
from operator import neg
print({x:x**2 for x in map(neg,range(5))})
print({x:x**2 for x in [-y for y in range(5)]})
print({x:x**2 for x in (-y for y in range(5))})
Rozumienie listy nie jest leniwe, więc może wymagać więcej pamięci (chyba że używasz generatora). Nawiasy kwadratowe [...]
często sprawiają, że wszystko staje się oczywiste, szczególnie gdy są w bałaganie w nawiasach. Z drugiej strony, czasami kończysz się na mówieniu jak pisanie [x for x in...
. Tak długo, jak długo zmienne iteratora są krótkie, opisy na liście są zwykle wyraźniejsze, jeśli nie wcinasz kodu. Ale zawsze możesz wciąć swój kod.
print(
{x:x**2 for x in (-y for y in range(5))}
)
lub rozbijać rzeczy:
rangeNeg5 = (-y for y in range(5))
print(
{x:x**2 for x in rangeNeg5}
)
Porównanie wydajności dla python3
map
jest teraz leniwy:
% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=map(f,xs)'
1000000 loops, best of 3: 0.336 usec per loop ^^^^^^^^^
Dlatego jeśli nie będziesz używać wszystkich swoich danych lub nie wiesz z góry, ile danych potrzebujesz, map
w python3 (i wyrażeniach generatora w python2 lub python3) unikniesz obliczania ich wartości do ostatniej niezbędnej chwili. Zwykle przeważa to zwykle nad kosztami użytkowania map
. Minusem jest to, że jest to bardzo ograniczone w Pythonie, w przeciwieństwie do większości języków funkcjonalnych: tę korzyść uzyskuje się tylko wtedy, gdy uzyskuje się dostęp do danych od lewej do prawej „w kolejności”, ponieważ wyrażenia generatora Pythona można oceniać tylko w kolejności x[0], x[1], x[2], ...
.
Powiedzmy jednak, że mamy gotową funkcję f
, którą chcielibyśmy map
, i ignorujemy lenistwo map
, natychmiast zmuszając do oceny list(...)
. Otrzymujemy bardzo interesujące wyniki:
% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(map(f,xs))'
10000 loops, best of 3: 165/124/135 usec per loop ^^^^^^^^^^^^^^^
for list(<map object>)
% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=[f(x) for x in xs]'
10000 loops, best of 3: 181/118/123 usec per loop ^^^^^^^^^^^^^^^^^^
for list(<generator>), probably optimized
% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(f(x) for x in xs)'
1000 loops, best of 3: 215/150/150 usec per loop ^^^^^^^^^^^^^^^^^^^^^^
for list(<generator>)
W rezultacie są w postaci AAA / BBB / CCC, gdzie A wykonano na stacji roboczej Intel około 2010 z pythonem 3.?.?, A B i C wykonano na stacji roboczej AMD około 2013 z pythonem 3.2.1, z bardzo innym sprzętem. W rezultacie wydaje się, że wyniki map i list są porównywalne pod względem wydajności, na co największy wpływ mają inne czynniki losowe. Wydaje się, że jedyne, co możemy powiedzieć, to to, że, o dziwo, podczas gdy spodziewamy się, że [...]
interpretacje list będą działały lepiej niż wyrażenia generatora (...)
, map
ODPOWIADAJĄ RÓWNIEŻ bardziej niż wyrażeniom generatora (ponownie zakładając, że wszystkie wartości są oceniane / używane).
Ważne jest, aby zdawać sobie sprawę, że testy te przyjmują bardzo prostą funkcję (funkcję tożsamości); jest to jednak w porządku, ponieważ gdyby funkcja była skomplikowana, wówczas narzut wydajności byłby znikomy w porównaniu z innymi czynnikami w programie. (Testowanie przy użyciu innych prostych rzeczy, takich jak f=lambda x:x+x
) może być interesujące
Jeśli jesteś biegły w czytaniu zestawu Python, możesz użyć dis
modułu, aby sprawdzić, czy tak naprawdę dzieje się za kulisami:
>>> listComp = compile('[f(x) for x in xs]', 'listComp', 'eval')
>>> dis.dis(listComp)
1 0 LOAD_CONST 0 (<code object <listcomp> at 0x2511a48, file "listComp", line 1>)
3 MAKE_FUNCTION 0
6 LOAD_NAME 0 (xs)
9 GET_ITER
10 CALL_FUNCTION 1
13 RETURN_VALUE
>>> listComp.co_consts
(<code object <listcomp> at 0x2511a48, file "listComp", line 1>,)
>>> dis.dis(listComp.co_consts[0])
1 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 18 (to 27)
9 STORE_FAST 1 (x)
12 LOAD_GLOBAL 0 (f)
15 LOAD_FAST 1 (x)
18 CALL_FUNCTION 1
21 LIST_APPEND 2
24 JUMP_ABSOLUTE 6
>> 27 RETURN_VALUE
>>> listComp2 = compile('list(f(x) for x in xs)', 'listComp2', 'eval')
>>> dis.dis(listComp2)
1 0 LOAD_NAME 0 (list)
3 LOAD_CONST 0 (<code object <genexpr> at 0x255bc68, file "listComp2", line 1>)
6 MAKE_FUNCTION 0
9 LOAD_NAME 1 (xs)
12 GET_ITER
13 CALL_FUNCTION 1
16 CALL_FUNCTION 1
19 RETURN_VALUE
>>> listComp2.co_consts
(<code object <genexpr> at 0x255bc68, file "listComp2", line 1>,)
>>> dis.dis(listComp2.co_consts[0])
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 17 (to 23)
6 STORE_FAST 1 (x)
9 LOAD_GLOBAL 0 (f)
12 LOAD_FAST 1 (x)
15 CALL_FUNCTION 1
18 YIELD_VALUE
19 POP_TOP
20 JUMP_ABSOLUTE 3
>> 23 LOAD_CONST 0 (None)
26 RETURN_VALUE
>>> evalledMap = compile('list(map(f,xs))', 'evalledMap', 'eval')
>>> dis.dis(evalledMap)
1 0 LOAD_NAME 0 (list)
3 LOAD_NAME 1 (map)
6 LOAD_NAME 2 (f)
9 LOAD_NAME 3 (xs)
12 CALL_FUNCTION 2
15 CALL_FUNCTION 1
18 RETURN_VALUE
Wydaje się, że lepiej jest używać [...]
składni niż list(...)
. Niestety map
klasa jest nieco nieprzejrzysta do demontażu, ale możemy to zrobić dzięki naszemu testowi prędkości.
map
i filter
wraz ze standardową biblioteką itertools
są z natury złe. O ile GvR nie powie, że to był albo straszny błąd, albo tylko wydajność, jedynym naturalnym wnioskiem, jeśli tak mówi „Pythonicness”, jest zapomnienie o tym, że jest głupie ;-)
map
filter
, że upuszczenie / to świetny pomysł na Python 3 , a tylko bunt innych Pythonistów utrzymywał je we wbudowanej przestrzeni nazw (podczas gdy reduce
został przeniesiony do functools
). Ja osobiście się nie zgadzam ( map
i nie mam nic przeciwko filter
wstępnie zdefiniowanym, szczególnie wbudowanym funkcjom, po prostu nigdy ich nie używam, jeśli lambda
byłoby to potrzebne), ale GvR od lat nazywa je zasadniczo nie Pythonicami.
itertools
? Część, którą cytuję w tej odpowiedzi, jest głównym twierdzeniem, które mnie otacza. Nie wiem, czy w jego idealnym świecie map
i filter
przeniósłby się do itertools
(lub functools
), czy też poszedłby całkowicie, ale cokolwiek by to nie było, kiedy ktoś powie, że itertools
jest to w ogóle nie mityczne, to tak naprawdę nie wiem, czym jest „Pythonic” powinno to znaczyć, ale nie sądzę, że może to być coś podobnego do „tego, co GvR zaleca ludziom”.
map
/ filter
, nie itertools
. Programowanie funkcjonalne doskonale pythonowy ( itertools
, functools
i operator
zostały zaprojektowane specjalnie z programowania funkcyjnego w umyśle i używam funkcjonalne idiomów w Pythonie cały czas), i itertools
zapewnia funkcje, które byłyby uciążliwe wdrożyć samemu, to specjalnie map
i filter
jest zbędny z wyrażeniami prądotwórczych przez co Guido ich nienawidzi. itertools
zawsze było dobrze.
map
i filter
zamiast wyrażeń listowych.Obiektywny powód, dlaczego wolisz je, chociaż nie są one „pythonowy” jest taka:
Wymagają one funkcje / lambdy jako argumenty, które wprowadzają nowe możliwości .
Ugryzło mnie to nie raz:
for x, y in somePoints:
# (several lines of code here)
squared = [x ** 2 for x in numbers]
# Oops, x was silently overwritten!
ale jeśli zamiast tego powiedziałbym:
for x, y in somePoints:
# (several lines of code here)
squared = map(lambda x: x ** 2, numbers)
wtedy wszystko byłoby dobrze.
Można powiedzieć, że byłem głupi, że używałem tej samej nazwy zmiennej w tym samym zakresie.
Nie byłem Kod był początkowo w porządku - dwa x
nie były w tym samym zakresie.
Dopiero po przeniesieniu wewnętrznego bloku do innej sekcji kodu pojawił się problem (czytaj: problem podczas konserwacji, a nie programowania) i nie spodziewałem się tego.
Tak, jeśli nigdy nie popełnisz tego błędu, lista wyrażeń będzie bardziej elegancka.
Ale z własnego doświadczenia (i widząc, że inni popełniają ten sam błąd) widziałem, że zdarzało się to wystarczająco często, że myślę, że nie jest to warte bólu, przez który musisz przejść, gdy te błędy wkradają się do twojego kodu.
Użyj map
i filter
. Zapobiegają subtelnym, trudnym do zdiagnozowania błędom związanym z zasięgiem.
Nie zapomnij rozważyć użycia imap
i ifilter
(in itertools
), jeśli są odpowiednie dla twojej sytuacji!
map
i / lub filter
. Jeśli cokolwiek, najbardziej bezpośrednim i logicznym tłumaczeniem, aby uniknąć problemu, jest nie map(lambda x: x ** 2, numbers)
wyrażenie generatora, list(x ** 2 for x in numbers)
które nie przecieka, jak już zauważył JeromeJ. Spójrz Mehrdad, nie bierz głosowania tak osobiście, po prostu zdecydowanie nie zgadzam się z twoim rozumowaniem tutaj.
W rzeczywistości map
wyrazy listowe zachowują się zupełnie inaczej w języku Python 3. Spójrz na następujący program Python 3:
def square(x):
return x*x
squares = map(square, [1, 2, 3])
print(list(squares))
print(list(squares))
Można się spodziewać, że wypisze dwukrotnie wiersz „[1, 4, 9]”, ale zamiast tego wypisze „[1, 4, 9]”, a następnie „[]”. Za pierwszym razem wygląda na squares
to, że zachowuje się jak sekwencja trzech elementów, ale za drugim razem jest pusta.
W języku Python 2 map
zwraca zwykłą, starą listę, podobnie jak w przypadku obu języków. Najważniejsze jest to, że zwracana wartość map
w Pythonie 3 (i imap
Pythonie 2) nie jest listą - to iterator!
Elementy są zużywane podczas iteracji w iteratorze, w przeciwieństwie do iteracji w liście. To dlatego squares
wygląda pusto w ostatnim print(list(squares))
wierszu.
Podsumowując:
map
stworzyć strukturę danych, a nie iterator. Ale może leniwe iteratory są łatwiejsze niż leniwe struktury danych. Jedzenie do namysłu. Dzięki @MnZrK
Uważam, że rozumienie list jest ogólnie bardziej wyraziste w stosunku do tego, co próbuję zrobić, niż map
- oboje to robią, ale ten pierwszy oszczędza obciążenia psychicznego związanego z próbą zrozumienia, co może być złożonym lambda
wyrażeniem.
Jest też gdzieś wywiad (nie mogę go znaleźć od razu), w którym Guido wymienia lambda
s i funkcje funkcjonalne jako rzecz, której najbardziej żałuje, przyjmując do Pythona, abyś mógł argumentować, że są nie Pythoniczni z powodu tego.
const
słowo kluczowe w C ++ jest wielkim triumfem w tych liniach.
lambda
zostały tak ułomne (brak stwierdzeń ...), że są trudne w użyciu i ograniczone.
Oto jeden możliwy przypadek:
map(lambda op1,op2: op1*op2, list1, list2)
przeciw:
[op1*op2 for op1,op2 in zip(list1,list2)]
Domyślam się, że zip () jest niefortunnym i niepotrzebnym narzutem, na który musisz sobie pozwolić, jeśli nalegasz na używanie list ze zrozumieniem zamiast mapy. Byłoby wspaniale, gdyby ktoś wyjaśnił to twierdząco lub negatywnie.
zip
lenistwo, używającitertools.izip
map(operator.mul, list1, list2)
. Na tych bardzo prostych wyrażeniach po lewej stronie nieporozumienia stają się niezdarne.
Jeśli planujesz napisać kod asynchroniczny, równoległy lub rozproszony, prawdopodobnie wolisz map
od zrozumienia listy - większość pakietów asynchronicznych, równoległych lub rozproszonych udostępnia map
funkcję do przeciążania pytona map
. Następnie przekazując odpowiednią map
funkcję do reszty kodu, może nie być konieczne modyfikowanie oryginalnego kodu seryjnego, aby działał równolegle (itp.).
Ponieważ Python 3 map()
jest iteratorem, musisz pamiętać o tym, czego potrzebujesz: iteratorze lub list
obiekcie.
Jak już wspomniano @AlexMartelli , map()
jest szybsze niż zrozumienie listy tylko wtedy, gdy nie używasz lambda
funkcji.
Przedstawię wam porównania czasu.
Python 3.5.2 i CPython
Użyłem notatnika Jupiter, a szczególnie %timeit
wbudowanej magicznej komendy
Pomiary : s == 1000 ms == 1000 * 1000 µs = 1000 * 1000 * 1000 ns
Ustawiać:
x_list = [(i, i+1, i+2, i*2, i-9) for i in range(1000)]
i_list = list(range(1000))
Wbudowana funkcja:
%timeit map(sum, x_list) # creating iterator object
# Output: The slowest run took 9.91 times longer than the fastest.
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 277 ns per loop
%timeit list(map(sum, x_list)) # creating list with map
# Output: 1000 loops, best of 3: 214 µs per loop
%timeit [sum(x) for x in x_list] # creating list with list comprehension
# Output: 1000 loops, best of 3: 290 µs per loop
lambda
funkcjonować:
%timeit map(lambda i: i+1, i_list)
# Output: The slowest run took 8.64 times longer than the fastest.
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 325 ns per loop
%timeit list(map(lambda i: i+1, i_list))
# Output: 1000 loops, best of 3: 183 µs per loop
%timeit [i+1 for i in i_list]
# Output: 10000 loops, best of 3: 84.2 µs per loop
Istnieje również coś takiego jak wyrażenie generatora, patrz PEP-0289 . Pomyślałem więc, że przydałoby się dodać to do porównania
%timeit (sum(i) for i in x_list)
# Output: The slowest run took 6.66 times longer than the fastest.
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 495 ns per loop
%timeit list((sum(x) for x in x_list))
# Output: 1000 loops, best of 3: 319 µs per loop
%timeit (i+1 for i in i_list)
# Output: The slowest run took 6.83 times longer than the fastest.
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 506 ns per loop
%timeit list((i+1 for i in i_list))
# Output: 10000 loops, best of 3: 125 µs per loop
list
obiektu:Użyj rozumienia listy, jeśli jest to funkcja niestandardowa, użyj list(map())
jeśli jest wbudowana funkcja
list
obiektu, wystarczy iterowalny:Zawsze używaj map()
!
Przeprowadziłem szybki test porównujący trzy metody wywoływania metody obiektu. Różnica czasu, w tym przypadku, jest znikoma i jest sprawą funkcji w pytaniu (zob @Alex martelli za odpowiedź ). Tutaj spojrzałem na następujące metody:
# map_lambda
list(map(lambda x: x.add(), vals))
# map_operator
from operator import methodcaller
list(map(methodcaller("add"), vals))
# map_comprehension
[x.add() for x in vals]
Spojrzałem na listy (przechowywane w zmiennej vals
) zarówno liczb całkowitych (Python int
), jak i liczb zmiennoprzecinkowych (Python float
) w celu zwiększenia rozmiarów list. Uwzględnia się następującą klasę manekina DummyNum
:
class DummyNum(object):
"""Dummy class"""
__slots__ = 'n',
def __init__(self, n):
self.n = n
def add(self):
self.n += 5
W szczególności add
metoda. __slots__
Cechą jest prosta optymalizacja Pythonie określić całkowitą ilość pamięci potrzebnej do klasy (cech), zmniejszając rozmiar pamięci. Oto wynikowe wykresy.
Jak wspomniano wcześniej, zastosowana technika stanowi minimalną różnicę i powinieneś kodować w sposób najbardziej czytelny dla Ciebie lub w szczególnych okolicznościach. W takim przypadku zrozumienie listy (map_comprehension
technika) jest najszybsze dla obu typów dodatków w obiekcie, szczególnie w przypadku krótszych list.
Odwiedź tę pastebin, aby znaleźć źródło użyte do wygenerowania wykresu i danych.
map
jest szybszy tylko wtedy, gdy funkcja jest wywoływana dokładnie w ten sam sposób (tj. [*map(f, vals)]
Vs. [f(x) for x in vals]
). Więc list(map(methodcaller("add"), vals))
jest szybszy niż [methodcaller("add")(x) for x in vals]
. map
może nie być szybszy, gdy zapętlony odpowiednik używa innej metody wywoływania, która może uniknąć pewnego narzutu (np. x.add()
pozwala uniknąć narzutu methodcaller
lub wyrażenia lambda). W tym konkretnym przypadku testowym [*map(DummyNum.add, vals)]
byłoby szybsze (ponieważ DummyNum.add(x)
i x.add()
zasadniczo mają taką samą wydajność).
list()
połączenia są nieco wolniejsze niż rozumienie listy. Dla uczciwego porównania musisz napisać [*map(...)]
.
list()
połączenia zwiększają koszty ogólne. Powinienem był poświęcić więcej czasu na czytanie odpowiedzi. Ponownie przeprowadzę te testy, aby uzyskać rzetelne porównanie, bez względu na to, jak niewielkie mogą być różnice.
Uważam, że najbardziej pythonicznym sposobem jest użycie rozumienia listy zamiast map
i filter
. Powodem jest to, że listy są bardziej zrozumiałe niż map
i filter
.
In [1]: odd_cubes = [x ** 3 for x in range(10) if x % 2 == 1] # using a list comprehension
In [2]: odd_cubes_alt = list(map(lambda x: x ** 3, filter(lambda x: x % 2 == 1, range(10)))) # using map and filter
In [3]: odd_cubes == odd_cubes_alt
Out[3]: True
Jak widzisz, zrozumienie nie wymaga dodatkowych lambda
wyrażeń w razie map
potrzeby. Co więcej, zrozumienie pozwala również na łatwe filtrowanie, podczas gdy map
wymaga filter
zezwolenia na filtrowanie.
Próbowałem kodu przez @ alex-martelli, ale znalazłem pewne rozbieżności
python -mtimeit -s "xs=range(123456)" "map(hex, xs)"
1000000 loops, best of 5: 218 nsec per loop
python -mtimeit -s "xs=range(123456)" "[hex(x) for x in xs]"
10 loops, best of 5: 19.4 msec per loop
mapa zajmuje tyle samo czasu, nawet w przypadku bardzo dużych zasięgów, podczas gdy korzystanie ze zrozumienia listy zajmuje dużo czasu, jak wynika z mojego kodu. Więc poza tym, że uważano mnie za „nie-mitycznego”, nie spotkałem się z żadnymi problemami z wydajnością związanymi z korzystaniem z mapy.
map
zwraca listę. W Pythonie 3 map
jest leniwie oceniany, więc po prostu wywołanie map
nie oblicza żadnego z nowych elementów listy, dlatego otrzymujesz tak krótkie czasy.