Понимание списка против карты


733

Есть ли причина, по которой вы предпочитаете использовать map()списки или наоборот? Является ли один из них более эффективным или считается более питоническим, чем другой?


8
Обратите внимание, что PyLint предупреждает, если вы используете карту вместо понимания списка, см. Сообщение W0141 .
поясничный

2
@lumbric, я не уверен, но это происходит, только если лямбда используется в карте.
0xc0de

Ответы:


662

mapможет быть микроскопически быстрее в некоторых случаях (когда вы НЕ делаете лямбду для этой цели, но используете ту же функцию в map и listcomp). Понимание списка может быть быстрее в других случаях, и большинство (не все) питонисты считают их более прямыми и понятными.

Пример крошечного преимущества карты в скорости при использовании точно такой же функции:

$ 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

Пример того, как сравнение производительности полностью меняется, когда карта нуждается в лямбда-выражении:

$ 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

39
Да, действительно, наше внутреннее руководство по стилю Python в действии явно рекомендует списки компоновки против карты и фильтра (даже не упоминая крошечную, но измеримую карту улучшения производительности, которая может дать в некоторых случаях ;-).
Алекс Мартелли

46
Не для того, чтобы разбирать точки бесконечного стиля Алекса, но иногда мне кажется, что карта легче читается: data = map (str, some_list_of_objects). Некоторые другие ... operator.attrgetter, operator.itemgetter и т. Д.
Грегг Линд

57
map(operator.attrgetter('foo'), objs)легче читать чем [o.foo for o in objs]?!
Алекс Мартелли

52
@ Алекс: Я предпочитаю не вводить ненужные имена, как oздесь, и ваши примеры показывают, почему.
Рейд Бартон

29
Я думаю, что @GreggLind имеет смысл, с его str()примером, хотя.
Эрик О Лебиго

474

случаи

  • Типичный случай : почти всегда вы захотите использовать списочное понимание в python, потому что это будет более очевидно, что вы делаете для начинающих программистов, читающих ваш код. (Это не относится к другим языкам, где могут применяться другие идиомы.) Будет даже более очевидно, что вы делаете с программистами на python, поскольку списочные выражения являются фактическим стандартом в python для итерации; они ожидаются .
  • Менее распространенный случай : однако, если у вас уже есть определенная функция , ее часто целесообразно использовать map, хотя она считается «непифонной». Например, map(sum, myLists)более элегантно / кратко, чем [sum(x) for x in myLists]. Вы получаете элегантность того, что вам не нужно составлять фиктивную переменную (например, sum(x) for x...или sum(_) for _...или sum(readableName) for readableName...), которую нужно вводить дважды, просто для итерации. Тот же аргумент справедлив для filterи reduceи всего из itertoolsмодуля: если у вас уже есть удобная функция, вы можете пойти дальше и заняться функциональным программированием. Это дает читабельность в некоторых ситуациях и теряет ее в других (например, начинающие программисты, несколько аргументов) ... но читаемость вашего кода в любом случае сильно зависит от ваших комментариев.
  • Почти никогда : вы можете захотеть использовать mapфункцию как чистую абстрактную функцию во время выполнения функционального программирования, когда вы отображаете mapили каррируете map, или иным образом извлекаете пользу из разговора mapкак функции. Например, в Haskell интерфейс с функтором fmapобобщает отображение по любой структуре данных. Это очень редко встречается в python, потому что грамматика python заставляет вас использовать генераторный стиль, чтобы говорить об итерации; Вы не можете легко обобщить это. (Это иногда хорошо, а иногда плохо.) Вероятно, вы можете придумать редкие примеры на python, в которых map(f, *lists)есть смысл. Самый близкий пример, который я могу придумать, был бы sumEach = partial(map,sum), который является однострочным, который очень приблизительно эквивалентен:

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • Просто используя for-loop : Вы также можете просто использовать цикл for. Хотя это не так элегантно с точки зрения функционального программирования, иногда нелокальные переменные делают код более понятным в императивных языках программирования, таких как python, потому что люди очень привыкли читать код таким образом. Циклы for также, как правило, наиболее эффективны, когда вы просто выполняете какую-либо сложную операцию, которая не строит список, например, списки и карты оптимизированы (например, суммирование, создание дерева и т. Д.) - по крайней мере эффективный с точки зрения памяти (не обязательно с точки зрения времени, где я бы в худшем случае ожидал постоянного фактора, за исключением некоторого редкого патологического сбоя при сборке мусора).

"Pythonism"

Мне не нравится слово «pythonic», потому что я не считаю, что pythonic всегда элегантен в моих глазах. Тем не менее, mapи filterи подобные функции (например, очень полезный itertoolsмодуль), вероятно, считаются непифоническими с точки зрения стиля.

Лень

С точки зрения эффективности, как и большинство функциональных программных конструкций, MAP МОЖЕТ БЫТЬ ЛЕННЫМ , и на самом деле ленив в Python. Это означает, что вы можете сделать это (в python3 ), и ваш компьютер не исчерпает память и потеряет все ваши несохраненные данные:

>>> map(str, range(10**100))
<map object at 0x2201d50>

Попробуйте сделать это с пониманием списка:

>>> [str(n) for n in range(10**100)]
# DO NOT TRY THIS AT HOME OR YOU WILL BE SAD #

Обращаем ваше внимание, что списочные выражения также по своей природе ленивы, но python решил реализовать их как не ленивые . Тем не менее, Python поддерживает ленивые списки в форме выражений генератора, как показано ниже:

>>> (str(n) for n in range(10**100))
<generator object <genexpr> at 0xacbdef>

Вы можете в основном думать о [...]синтаксисе как о передаче выражения генератора в конструктор списка, например list(x for x in range(5)).

Краткий надуманный пример

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))})

Понимания списков не ленивы, поэтому могут потребовать больше памяти (если вы не используете генератор пониманий). Квадратные скобки [...]часто делают вещи очевидными, особенно когда в беспорядке скобок. С другой стороны, иногда вы становитесь многословным, как печатать [x for x in.... До тех пор, пока вы сохраняете свои переменные итератора короткими, списки обычно более понятны, если вы не делаете отступ в коде. Но вы всегда можете сделать отступ для своего кода.

print(
    {x:x**2 for x in (-y for y in range(5))}
)

или разбить вещи:

rangeNeg5 = (-y for y in range(5))
print(
    {x:x**2 for x in rangeNeg5}
)

Сравнение эффективности для python3

map сейчас лень

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=map(f,xs)'
1000000 loops, best of 3: 0.336 usec per loop            ^^^^^^^^^

Поэтому, если вы не будете использовать все свои данные или заранее не знаете, сколько данных вам нужно, mapв python3 (и в выражениях генератора в python2 или python3) не будет вычисляться их значение до последнего необходимого момента. Обычно это перевешивает любые накладные расходы от использования map. Недостатком является то, что в python это очень ограничено по сравнению с большинством функциональных языков: вы получаете это преимущество, только если обращаетесь к своим данным слева направо «по порядку», потому что выражения генератора питона могут оцениваться только в порядке x[0], x[1], x[2], ....

Однако предположим, что у нас есть готовая функция, которую fмы хотели бы выполнять map, и мы игнорируем лень map, немедленно форсируя оценку list(...). Мы получаем очень интересные результаты:

% 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>)

Результаты представлены в виде AAA / BBB / CCC, где A был выполнен на рабочей станции Intel около 2010 года с python 3.?.?, А B и C были выполнены на рабочей станции AMD около 2013 года с python 3.2.1, с совершенно другим оборудованием. В результате получается, что представления карт и списков сопоставимы по производительности, что сильнее всего зависит от других случайных факторов. Единственное, что мы можем сказать, это то, что, как ни странно, хотя мы ожидаем, что понимание списка [...]будет работать лучше, чем выражения генератора (...), mapТАКЖЕ более эффективно, чем выражения генератора (опять же, предполагая, что все значения вычисляются / используются).

Важно понимать, что эти тесты предполагают очень простую функцию (тождественную функцию); однако это хорошо, потому что если бы функция была сложной, то потери производительности были бы незначительными по сравнению с другими факторами в программе. (Это может все еще быть интересно проверить с другими простыми вещами как f=lambda x:x+x)

Если вы хорошо разбираетесь в сборке Python, вы можете использовать disмодуль, чтобы увидеть, действительно ли это происходит за кулисами:

>>> 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 

Кажется, лучше использовать [...]синтаксис, чем list(...). К сожалению, mapкласс немного непрозрачен для разборки, но мы можем сделать это с нашим тестом скорости.


5
«Очень полезный модуль itertools [вероятно] считается непитоническим с точки зрения стиля». Хм. Мне также не нравится термин «Pythonic», так что в некотором смысле меня не волнует, что он означает, но я не думаю, что это справедливо по отношению к тем, кто его использует, говорить, что согласно встроенным «Pythonicness» mapи filterНаряду со стандартной библиотекой itertoolsпо сути плохой стиль. Если GvR на самом деле не говорит, что они были либо ужасной ошибкой, либо исключительно для производительности, единственный естественный вывод, если это то, что говорит «Pythonicness», это забыть об этом как о глупом ;-)
Steve Jessop

4
@SteveJessop: На самом деле, Гвидо думалmapfilter , что удаление / было отличной идеей для Python 3 , и только восстание других Pythonistas удерживало их во встроенном пространстве имен (пока оно reduceбыло перенесено в functools). Я лично не согласен ( mapи filterхорошо с предопределенными, особенно встроенными, функциями, просто никогда не использую их, если lambdaпотребуется), но GvR в основном называл их не Pythonic в течение многих лет.
ShadowRanger

@ShadowRanger: правда, но GvR когда-нибудь планировал удалить itertools? Часть, которую я цитирую из этого ответа, является основным утверждением, которое смущает меня. Я не знаю, находится ли в его идеальном мире mapи filterбудет ли двигаться itertools(или functools) или идти целиком, но в зависимости от того, в itertoolsкаком случае это происходит, как только кто-то говорит, что он не является пифоническим, тогда я действительно не знаю, что такое «питон» должно означать, но я не думаю, что это может быть что-то похожее на «то, что GvR рекомендует людям использовать».
Стив Джессоп

2
@ SteveJessop: я только обращался к map/ filter, нет itertools. Функциональное программирование прекрасно Pythonic ( itertools, functoolsи operatorбыли разработаны специально с функциональным программированием в виду, и я использую функциональные идиомы в Python все время), и itertoolsпредоставляет возможности , которые были бы боль , чтобы реализовать себя, Это конкретно mapи filterбыть избыточными с выражениями генератора это заставило Гвидо ненавидеть их. itertoolsвсегда было хорошо.
ShadowRanger

1
Я мог бы любить этот ответ, если бы был способ. Хорошо объяснил.
NelsonGon

95

Python 2: Вы должны использовать mapи filterвместо списков.

Цель причина , почему вы должны предпочесть их , даже если они не являются «Pythonic» заключается в следующем:
они требуют функции / лямбды в качестве аргументов, которые ввести новую область .

Я был укушен этим не раз:

for x, y in somePoints:
    # (several lines of code here)
    squared = [x ** 2 for x in numbers]
    # Oops, x was silently overwritten!

но если бы вместо этого я сказал:

for x, y in somePoints:
    # (several lines of code here)
    squared = map(lambda x: x ** 2, numbers)

тогда все было бы хорошо.

Можно сказать, что я глуп, что использовал одно и то же имя переменной в той же области видимости.

Я не был Изначально код был в порядке - xони не были в одной области видимости.
Только после того, как я переместил внутренний блок в другой раздел кода, возникла проблема (читай: проблема во время обслуживания, а не разработки), и я этого не ожидал.

Да, если вы никогда не совершите эту ошибку, тогда списочные представления будут более элегантными.
Но по личному опыту (и по тому, как другие видят ту же ошибку), я видел, что такое случалось достаточно много раз, и я думаю, что это не стоит той боли, которую вам придется пережить, когда эти ошибки проникают в ваш код.

Вывод:

Используйте mapи filter. Они предотвращают тонкие, трудно диагностируемые ошибки, связанные с областью действия.

Примечание:

Не забудьте рассмотреть возможность использования imapи ifilteritertools), если они подходят для вашей ситуации!


7
Спасибо за указание на это. Мне не пришло в голову, что понимание списка находится в той же области и может быть проблемой. С учетом сказанного, я думаю, что некоторые другие ответы дают понять, что понимание списка должно быть подходом по умолчанию в большинстве случаев, но это то, что нужно помнить. Это также хорошее общее напоминание о том, что функции (и, следовательно, область действия) должны быть небольшими, иметь тщательные модульные тесты и использовать операторы assert.
TimothyAWiseman

13
@wim: Это было только для Python 2, хотя это относится к Python 3, если вы хотите оставаться обратно совместимым. Я знал об этом и некоторое время использовал Python (да, больше, чем несколько месяцев), и все же это случилось со мной. Я видел других, которые умнее меня, попадали в ту же ловушку. Если вы настолько ярки и / или опытны, что это не проблема для вас, тогда я рад за вас, я не думаю, что большинство людей такие же, как вы. Если бы они были, не было бы такого желания исправить это в Python 3.
user541686

12
Извините, но вы написали это в конце 2012 года, задолго до появления Python 3, и ответ читается так, как будто вы рекомендуете непопулярный стиль кодирования на Python только потому, что вас укусила ошибка во время резки и Вставить код. Я никогда не претендовал на то, чтобы быть умным или опытным, я просто не согласен с тем, что смелое утверждение оправдано вашими причинами.
Вим

8
@ Wim: А? Python 2 все еще используется во многих местах, тот факт, что Python 3 существует, не меняет этого. И когда вы говорите «это не совсем тонкая ошибка для любого, кто использовал Python более нескольких месяцев», это предложение буквально означает «это касается только неопытных разработчиков» (явно не вы). И, между прочим, вы явно не читали ответ, потому что я выделил жирным шрифтом, что я перемещал , а не копировал код. Ошибки копирования и вставки одинаковы для разных языков. Этот тип ошибок является более уникальным для Python из-за его области видимости; это тоньше и легче забыть и пропустить.
user541686

3
Это все еще не логичная причина для переключения на mapи / или filter. Во всяком случае, самый прямой и логичный перевод, чтобы избежать вашей проблемы, это не map(lambda x: x ** 2, numbers)выражение генератора, list(x ** 2 for x in numbers)которое не просачивается, как уже указывал JeromeJ. Послушай, Мердад, не воспринимай это лично так глубоко, я просто не согласен с твоими рассуждениями.
Wim

46

На самом деле, mapи понимание списка ведет себя совсем по-другому в языке Python 3. Взгляните на следующую программу Python 3:

def square(x):
    return x*x
squares = map(square, [1, 2, 3])
print(list(squares))
print(list(squares))

Вы можете ожидать, что он напечатает строку «[1, 4, 9]» дважды, но вместо этого он напечатает «[1, 4, 9]», а затем «[]». Первый раз, когда вы смотрите на squaresнего, он выглядит как последовательность из трех элементов, но во второй раз как пустой.

В языке Python 2 mapвозвращается простой старый список, точно так же, как в обоих языках. Суть в том, что возвращаемое значение mapв Python 3 (и imapв Python 2) не является списком - это итератор!

Элементы потребляются, когда вы перебираете итератор в отличие от того, когда вы перебираете список. Вот почему squaresвыглядит пустым в последней print(list(squares))строке.

Обобщить:

  • При работе с итераторами вы должны помнить, что они находятся в состоянии и что они изменяются по мере их обхода.
  • Списки более предсказуемы, поскольку они изменяются только при явном их изменении; они контейнеры .
  • И бонус: числа, строки и кортежи еще более предсказуемы, так как они не могут измениться вообще; это ценности .

это, вероятно, лучший аргумент для понимания списка. Карта питонов - это не функциональная карта, а искалеченный рыжий пасынок функциональной реализации. Очень грустно, потому что я действительно не люблю понимания.
полуомант

@semiomant Я бы сказал, что lazy map (как в python3) более «функциональный», чем нетерпеливая карта (как в python2). Например, карта в Haskell ленива (ну, все в Haskell ленива ...). В любом случае, ленивая карта лучше подходит для связывания карт - если у вас есть карта, примененная к карте, примененная к карте, у вас есть список для каждого из промежуточных вызовов карты в python2, тогда как в python3 у вас есть только один результирующий список, так что он более эффективен для памяти ,
MnZrK

Я предполагаю, что я хочу mapсоздать структуру данных, а не итератор. Но, может быть, ленивые итераторы проще, чем ленивые структуры данных. Пища для размышлений. Спасибо @MnZrK
полуомант

Вы хотите сказать, что карта возвращает итеративный, а не итератор.
user541686

16

Я нахожу, что понимание списков, как правило, более выразительно в отношении того, что я пытаюсь сделать, чем map- они оба делают это, но первое спасает умственную нагрузку от попыток понять, что может быть сложным lambdaвыражением.

Также есть где-то интервью (я не могу его найти случайно), где Гвидо перечисляет lambdas и функциональные функции как то, что он больше всего сожалеет о принятии в Python, так что вы можете сделать аргумент, что они непиратонны в силу того, что.


9
Да, вздох, но первоначальное намерение Гвидо полностью удалить лямбду в Python 3 получило массу лоббирования против нее, поэтому он вернулся к ней, несмотря на мою решительную поддержку - ну, думаю, лямбда слишком удобна во многих ПРОСТЫХ случаях, единственное проблема в том, что он выходит за пределы SIMPLE или получает имя (в последнем случае это глупый дубликат def! -).
Алекс Мартелли

1
Вот интервью, о котором вы думаете, это: amk.ca/python/writing/gvr-interview , где Гвидо говорит: «Иногда я слишком быстро принимал вклады, а потом понял, что это было ошибкой. Одним из примеров будет некоторые функции функционального программирования, такие как лямбда-функции. lambda - это ключевое слово, которое позволяет вам создать небольшую анонимную функцию, встроенные функции, такие как map, filter, и limit, запустить функцию для типа последовательности, такого как список. "
Дж. Тейлор

3
@ Алекс, у меня нет твоего многолетнего опыта, но я видел гораздо более сложные списки, чем лямбды. Конечно, злоупотреблять языковыми особенностями всегда трудно соблазн. Интересно, что списочные представления (эмпирически) кажутся более склонными к злоупотреблениям, чем лямбды, хотя я не уверен, почему так должно быть. Я также укажу, что «хромать» не всегда плохо. Сокращение объема «вещей, которые эта линия может делать» иногда может облегчить читателю. Например, constключевое слово в C ++ является большим триумфом в этом направлении.
Стюарт Берг

> Гвидо. Что является еще одним доказательством того, что Гвидо не в своем уме. Конечно lambda, они были сделаны настолько неубедительными (без заявлений ...), что их сложно использовать и в любом случае они ограничены.
Джавадба

16

Вот один из возможных случаев:

map(lambda op1,op2: op1*op2, list1, list2)

против:

[op1*op2 for op1,op2 in zip(list1,list2)]

Я предполагаю, что zip () - это печальные и ненужные накладные расходы, которые нужно потворствовать, если вы настаиваете на использовании списочных представлений вместо карты. Было бы замечательно, если бы кто-то разъяснил это положительно или отрицательно.


«[op1 * op2 из op1, op2 в zip (list1, list2)]» | s / form / for / И эквивалентный список без zip: (менее читаемый) [list1 [i] * list2 [i] для i в диапазоне (len (
list1

2
Должно быть "for" not "from" в вашей второй цитате кода @andz, а также в комментарии @ weakish. Я думал, что обнаружил новый синтаксический подход к списку пониманий ... Черт.
физика Майкл

4
чтобы добавить очень поздний комментарий, вы можете сделать его zipленивым, используяitertools.izip
tacaswell

5
Я думаю, что я все еще предпочитаю map(operator.mul, list1, list2). Именно на этих очень простых выражениях в левой части понимание становится неуклюжим.
Ян Вернье

1
Я не осознавал, что карта может принимать несколько итераций в качестве входных данных для своей функции и, таким образом, может избежать zip.
ло

16

Если вы планируете писать какой-либо асинхронный, параллельный или распределенный код, вы, вероятно, предпочтете mapпонимание списка - поскольку большинство асинхронных, параллельных или распределенных пакетов предоставляют mapфункцию для перегрузки Python map. Затем, передав соответствующую mapфункцию остальной части вашего кода, вам, возможно, не придется изменять исходный последовательный код, чтобы он работал параллельно (и т. Д.).



1
Модуль многопроцессорной обработки Python делает это: docs.python.org/2/library/multiprocessing.html
Роберт Л.

9

Так как Python 3 map()является итератором, вам нужно помнить, что вам нужно: итератор или listобъект.

Как уже упоминалось @AlexMartelli , map()это быстрее, чем понимание списка, только если вы не используете lambdaфункцию.

Я представлю вам некоторые сравнения времени.

Python 3.5.2 и CPython
Я использовал ноутбук Jupiter и особенно %timeitвстроенную магическую команду.
Измерения : s == 1000 мс == 1000 * 1000 мкс = 1000 * 1000 * 1000 нс.

Настроить:

x_list = [(i, i+1, i+2, i*2, i-9) for i in range(1000)]
i_list = list(range(1000))

Встроенная функция:

%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 функция:

%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

Существует также такая вещь, как выражение генератора, см. PEP-0289 . Поэтому я подумал, что было бы полезно добавить его в сравнение.

%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объект:

Используйте понимание списка, если это пользовательская функция, используйте, list(map())если есть встроенная функция

Вам не нужен listобъект, вам нужен только повторяемый:

Всегда используйте map()!


1

Я провел быстрый тест, сравнивая три метода для вызова метода объекта. Разница во времени в этом случае незначительна и зависит от рассматриваемой функции (см. Ответ @Alex Martelli ). Здесь я посмотрел на следующие методы:

# 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]

Я просмотрел списки (хранящиеся в переменной vals) как целых чисел (Python int), так и чисел с плавающей запятой (Python float) для увеличения размеров списков. Следующий фиктивный класс DummyNumсчитается:

class DummyNum(object):
    """Dummy class"""
    __slots__ = 'n',

    def __init__(self, n):
        self.n = n

    def add(self):
        self.n += 5

Конкретно addметод. __slots__Атрибут является простой оптимизацией в Python , чтобы определить общий объем памяти , необходимый класс (атрибуты), уменьшая размер памяти. Вот итоговые сюжеты.

Производительность отображения методов объекта Python

Как указывалось ранее, используемая техника имеет минимальное значение, и вы должны кодировать ее наиболее удобным для вас способом или в определенных обстоятельствах. В этом случае понимание списка ( map_comprehensionтехника) является самым быстрым для обоих типов дополнений в объекте, особенно с более короткими списками.

Посетите этот каталог для источника, использованного для создания графика и данных.


1
Как уже объяснялось в других ответах, mapбыстрее, только если функция вызывается точно таким же образом (т.е. [*map(f, vals)]против [f(x) for x in vals]). Так list(map(methodcaller("add"), vals))быстрее чем [methodcaller("add")(x) for x in vals]. mapможет не быть быстрым, когда дублирующийся элемент использует другой вызывающий метод, который может избежать некоторых издержек (например, x.add()избежать methodcallerиздержек или лямбда-выражений). Для этого конкретного теста, [*map(DummyNum.add, vals)]будет быстрее (потому что DummyNum.add(x)и в x.add()основном имеют одинаковую производительность).
GZ0

1
Кстати, явные list()вызовы немного медленнее, чем списки. Для честного сравнения нужно написать [*map(...)].
GZ0

@ GZ0 спасибо за отличный отзыв! Все имеет смысл, и я не знал, что list()звонки увеличились накладные расходы. Должен был потратить больше времени на чтение ответов. Я проведу эти тесты повторно для достоверного сравнения, однако различия могут быть незначительными.
craymichael

1

Я попробовал код @ alex-martelli, но обнаружил некоторые несоответствия

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

map занимает столько же времени даже для очень больших диапазонов, в то время как использование списков занимает много времени, как видно из моего кода. Таким образом, помимо того, что меня считают «непифоничным», я не сталкивался с проблемами производительности, связанными с использованием карты.


4
Это очень старый вопрос, и ответ, на который вы ссылаетесь, был, скорее всего, написан со ссылкой на Python 2, где mapвозвращается список. В Python 3 mapлениво оценивается, поэтому простой вызов mapне вычисляет ни один из новых элементов списка, поэтому вы получаете такие короткие промежутки времени.
kaya3

Я думаю, что вы используете Python 3.x Когда я задал этот вопрос, Python 3 был выпущен совсем недавно, и Python 2.x был очень стандартным.
TimothyAWiseman

0

Я считаю, что самый Pythonic способ состоит в том, чтобы использовать понимание списка вместо mapи filter. Причина в том, что списки понятнее, чем mapи 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

Как видите, понимание не требует дополнительных lambdaвыражений по мере mapнеобходимости. Кроме того, понимание также позволяет легко фильтровать, в то время как mapтребует filterразрешить фильтрацию.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.