Перерахуйте розуміння проти карти


732

Чи є причина віддавати перевагу використанню map()над розумінням списку чи навпаки? Чи будь-який з них, як правило, більш ефективний чи вважається загалом більш пітонічним, ніж інший?


8
Зверніть увагу, що PyLint попереджає, якщо ви використовуєте карту замість розуміння списку, див. Повідомлення W0141 .
мастило

2
@lumbric, я не впевнений, але це робиться лише в тому випадку, якщо лямбда використовується в карті.
0xc0de

Відповіді:


660

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 змушує вас використовувати стиль генератора, щоб говорити про ітерацію; ви не можете легко узагальнити це. (Це іноді добре, а іноді і погано.) Ви, мабуть, можете знайти рідкісні приклади пітонів, де map(f, *lists)це розумно робити. Найближчим прикладом, який я можу придумати, може бути sumEach = partial(map,sum), який є однолінійним, який приблизно відповідає:

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

"Піфонізм"

Мені не подобається слово "пітонік", тому що я не знаю, що пітонік завжди елегантний в моїх очах. Тим не менш, mapі filterподібні функції (як дуже корисний itertoolsмодуль), мабуть, вважаються непітонічними з точки зору стилю.

Лінь

З точки зору ефективності, як і більшість функціональних програм програмування, МАП МОЖЕТ ЛІЗИТИ , а насправді лінивий у пітоні. Це означає, що ви можете це зробити (у 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 підтримує розуміння лінивого списку у вигляді виразів генератора таким чином:

>>> (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 circa-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 [, ймовірно, вважається непітонічним з точки зору стилю". Хм. Мені також не подобається термін "піфонічний", тому в деякому сенсі мені байдуже, що це означає, але я не думаю, що це справедливо для тих, хто ним користується, щоб сказати, що відповідно до "Pythonicness" вбудовані mapі filterпоряд зі стандартною бібліотекою itertoolsсуттєво поганий стиль. Якщо GvR насправді не каже, що вони були або жахливою помилкою, або виключно для виконання, єдиний природний висновок, якщо саме так говорить "Pythonicness" - це забути про це як про дурне ;-)
Стів Джессоп

4
@SteveJessop: Насправді, Guido думав, що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та ifilter(in itertools), якщо вони відповідають вашій ситуації!


7
Дякуємо, що вказали на це. Мені явно не прийшло в голову, що розуміння списку знаходиться в тому ж обсязі і може бути проблемою. Зважаючи на це, я думаю, що деякі інші відповіді дають зрозуміти, що розуміння списків має бути підходом за замовчуванням більшу частину часу, але що це щось, що слід пам’ятати. Це також хороше загальне нагадування про те, щоб функції (і, таким чином, сфера застосування) мали малі та ретельні тестові одиниці та використовували твердження твердження.
TimothyAWiseman

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

12
Вибачте, але ви написали це наприкінці 2012 року, після того, як python 3 з'явиться на сцені, і відповідь звучить так, що ви рекомендуєте інакше непопулярний стиль кодування python лише тому, що вас покусав помилка під час різання та вставлення коду. Я ніколи не претендував на яскравий або досвідчений досвід, я просто не згоден, що сміливе твердження виправдане вашими причинами.
wim

8
@wim: А? Python 2 все ще використовується в багатьох місцях, але факт, що існує Python 3, це не змінює. І коли ви говорите "це не зовсім тонка помилка для тих, хто використовує Python більше декількох місяців", це речення буквально означає "це стосується лише недосвідчених розробників" (явно не ви). А для запису ви чітко не прочитали відповіді, тому що я жирним шрифтом сказав, що рухаюсь , а не копіюю код. Помилки копіювання та вставки є однаковими для різних мов. Цей вид помилок більш унікальний для Python через його обхват; тонкіше і простіше забути і пропустити.
користувач541686

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 Я б сказав, що ледача карта (як у python3) є більш "функціональною", ніж жадана карта (як у python2). Наприклад, карта в Haskell лінива (ну, все в Haskell ліниво ...). У будь-якому випадку, ледача карта краще для ланцюга карт - якщо у вас застосована карта, застосована до карти, у вас є список для кожного проміжного дзвінка на карті в python2, тоді як у python3 у вас є лише один результат, завдяки чому його більш ефективна пам'ять .
MnZrK

Я думаю, що я хочу, mapщоб створити структуру даних, а не ітератор. Але, можливо, ліниві ітератори простіші, ніж ледачі структури даних. Їжа для роздумів. Дякую @MnZrK
семіоман

Ви хочете сказати, що карта повертає ітерабельний, а не ітератор.
користувач541686

16

Я вважаю, що розуміння списку, як правило, більше виражає те, що я намагаюся зробити, ніж те, що mapвони обидва роблять, але колишній економить розумовий навантаження, намагаючись зрозуміти, що може бути складним lambdaвиразом.

Там же є десь інтерв'ю (я не можу його знайти навпіл), де Гвідо перераховує lambdas та функціональні функції як річ, яку він найбільше шкодує про прийняття в Python, так що ви можете зробити аргумент того, що вони не пітонічні в силу того.


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

1
Інтерв'ю, про яке ви думаєте, саме таке: amk.ca/python/writing/gvr-interview , де Гвідо каже: "Іноді я занадто швидко
Дж. Тейлор

3
@ Алекс, я не маю твого багаторічного досвіду, але я бачив набагато більш складні розуміння списку, ніж лямбда. Звичайно, зловживати мовними особливостями завжди є складною спокусою протистояти. Цікаво, що розуміння списків (емпірично) здається більш схильним до зловживань, ніж лямбда, хоча я не впевнений, чому так має бути. Я також зазначу, що "погризти" не завжди є поганою справою. Скорочення обсягу "речей, які може робити цей рядок" іноді може полегшити для читача. Наприклад, constключове слово в C ++ є чудовим тріумфом у цих напрямках.
Стюарт Берг

> гідо. Це ще один доказ того, що Гвідо поза розумом. Звичайно lambda, вони були зроблені настільки кульгавими (немає заяв ..), що їх важко використовувати і обмежити в будь-якому випадку.
javadba

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 / І еквівалентний список із поштовим відривом: (менш читабельний) [list1 [i] * list2 [i] для i в діапазоні (len (list1))]
слабкий

2
У вашому другому цитаті коду має бути "за" не "від" @andz та в коментарі @ slabish також. Я думав, що я виявив новий синтаксичний підхід до списку розумінь ... Дарн.
Physicsmichael

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

5
Я думаю, що все одно віддаю перевагу map(operator.mul, list1, list2). Саме в цих дуже простих лівих бокових виразах розуміння стають незграбними.
Янна Верньє

1
Я не розумів, що карта може приймати кілька ітерабелів як вхід для своєї функції, і, таким чином, могла б уникнути поштового відбитку.
блі

16

Якщо ви плануєте писати будь-який асинхронний, паралельний або розподілений код, ви, мабуть, віддасте перевагу mapперед розумінням списку - оскільки більшість асинхронних, паралельних або розподілених пакетів забезпечують mapфункцію перевантаження пітона map. Тоді, передаючи відповідну mapфункцію решті коду, можливо, вам не доведеться змінювати свій початковий серійний код, щоб він працював паралельно (тощо).



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

9

Отже, оскільки Python 3 map()є ітератором, ви повинні мати на увазі, що вам потрібно: ітератор або listоб’єкт.

Як вже зазначалося @AlexMartelli , map()швидше, ніж розуміння списку, лише якщо ви не використовуєте lambdaфункцію.

Я представлю вам порівняння часу.

Python 3.5.2 і CPython
Я використовував ноутбук Юпітера і особливо %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 Мартеллі в відповідь ). Тут я розглянув такі методи:

# 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

0

Я вважаю, що найбільш пітонічним способом є використання розуміння списку замість 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дозволити фільтрацію.


0

Я спробував код @ 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

карта займає стільки ж часу навіть для дуже великих діапазонів, а використання розуміння списку займає багато часу, як видно з мого коду. Тому, окрім того, що вважався "непітонічним", я не стикався з жодними проблемами продуктивності, пов'язаними з використанням карти.


3
Це дуже давнє запитання, і відповідь, на який ви посилаєтесь, дуже ймовірно був написаний у посиланні на Python 2, де mapповертає список. У Python 3 mapоцінюється ліниво, тому просто виклик mapне обчислює жоден з нових елементів списку, отже, чому ви отримуєте такі короткі терміни.
kaya3

Я думаю, що ви використовуєте Python 3.x Коли я задав це питання, Python 3 був нещодавно випущений, а Python 2.x був дуже стандартним.
TimothyAWiseman
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.