Есть ли причина, по которой вы предпочитаете использовать map()
списки или наоборот? Является ли один из них более эффективным или считается более питоническим, чем другой?
python
list-comprehension
map-function
TimothyAWiseman
источник
источник
Ответы:
map
может быть микроскопически быстрее в некоторых случаях (когда вы НЕ делаете лямбду для этой цели, но используете ту же функцию в map и listcomp). Понимание списка может быть быстрее в других случаях, и большинство (не все) питонисты считают их более прямыми и понятными.Пример крошечного преимущества карты в скорости при использовании точно такой же функции:
Пример того, как сравнение производительности полностью меняется, когда карта нуждается в лямбда-выражении:
источник
map(operator.attrgetter('foo'), objs)
легче читать чем[o.foo for o in objs]
?!o
здесь, и ваши примеры показывают, почему.str()
примером, хотя.случаи
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)
, который является однострочным, который очень приблизительно эквивалентен:for
-loop : Вы также можете просто использовать цикл for. Хотя это не так элегантно с точки зрения функционального программирования, иногда нелокальные переменные делают код более понятным в императивных языках программирования, таких как python, потому что люди очень привыкли читать код таким образом. Циклы for также, как правило, наиболее эффективны, когда вы просто выполняете какую-либо сложную операцию, которая не строит список, например, списки и карты оптимизированы (например, суммирование, создание дерева и т. Д.) - по крайней мере эффективный с точки зрения памяти (не обязательно с точки зрения времени, где я бы в худшем случае ожидал постоянного фактора, за исключением некоторого редкого патологического сбоя при сборке мусора)."Pythonism"
Мне не нравится слово «pythonic», потому что я не считаю, что pythonic всегда элегантен в моих глазах. Тем не менее,
map
иfilter
и подобные функции (например, очень полезныйitertools
модуль), вероятно, считаются непифоническими с точки зрения стиля.Лень
С точки зрения эффективности, как и большинство функциональных программных конструкций, MAP МОЖЕТ БЫТЬ ЛЕННЫМ , и на самом деле ленив в Python. Это означает, что вы можете сделать это (в python3 ), и ваш компьютер не исчерпает память и потеряет все ваши несохраненные данные:
Попробуйте сделать это с пониманием списка:
Обращаем ваше внимание, что списочные выражения также по своей природе ленивы, но python решил реализовать их как не ленивые . Тем не менее, Python поддерживает ленивые списки в форме выражений генератора, как показано ниже:
Вы можете в основном думать о
[...]
синтаксисе как о передаче выражения генератора в конструктор списка, напримерlist(x for x in range(5))
.Краткий надуманный пример
Понимания списков не ленивы, поэтому могут потребовать больше памяти (если вы не используете генератор пониманий). Квадратные скобки
[...]
часто делают вещи очевидными, особенно когда в беспорядке скобок. С другой стороны, иногда вы становитесь многословным, как печатать[x for x in...
. До тех пор, пока вы сохраняете свои переменные итератора короткими, списки обычно более понятны, если вы не делаете отступ в коде. Но вы всегда можете сделать отступ для своего кода.или разбить вещи:
Сравнение эффективности для python3
map
сейчас леньПоэтому, если вы не будете использовать все свои данные или заранее не знаете, сколько данных вам нужно,
map
в python3 (и в выражениях генератора в python2 или python3) не будет вычисляться их значение до последнего необходимого момента. Обычно это перевешивает любые накладные расходы от использованияmap
. Недостатком является то, что в python это очень ограничено по сравнению с большинством функциональных языков: вы получаете это преимущество, только если обращаетесь к своим данным слева направо «по порядку», потому что выражения генератора питона могут оцениваться только в порядкеx[0], x[1], x[2], ...
.Однако предположим, что у нас есть готовая функция, которую
f
мы хотели бы выполнятьmap
, и мы игнорируем леньmap
, немедленно форсируя оценкуlist(...)
. Мы получаем очень интересные результаты:Результаты представлены в виде AAA / BBB / CCC, где A был выполнен на рабочей станции Intel около 2010 года с python 3.?.?, А B и C были выполнены на рабочей станции AMD около 2013 года с python 3.2.1, с совершенно другим оборудованием. В результате получается, что представления карт и списков сопоставимы по производительности, что сильнее всего зависит от других случайных факторов. Единственное, что мы можем сказать, это то, что, как ни странно, хотя мы ожидаем, что понимание списка
[...]
будет работать лучше, чем выражения генератора(...)
,map
ТАКЖЕ более эффективно, чем выражения генератора (опять же, предполагая, что все значения вычисляются / используются).Важно понимать, что эти тесты предполагают очень простую функцию (тождественную функцию); однако это хорошо, потому что если бы функция была сложной, то потери производительности были бы незначительными по сравнению с другими факторами в программе. (Это может все еще быть интересно проверить с другими простыми вещами как
f=lambda x:x+x
)Если вы хорошо разбираетесь в сборке Python, вы можете использовать
dis
модуль, чтобы увидеть, действительно ли это происходит за кулисами:Кажется, лучше использовать
[...]
синтаксис, чемlist(...)
. К сожалению,map
класс немного непрозрачен для разборки, но мы можем сделать это с нашим тестом скорости.источник
map
иfilter
Наряду со стандартной библиотекойitertools
по сути плохой стиль. Если GvR на самом деле не говорит, что они были либо ужасной ошибкой, либо исключительно для производительности, единственный естественный вывод, если это то, что говорит «Pythonicness», это забыть об этом как о глупом ;-)map
filter
, что удаление / было отличной идеей для Python 3 , и только восстание других Pythonistas удерживало их во встроенном пространстве имен (пока оноreduce
было перенесено вfunctools
). Я лично не согласен (map
иfilter
хорошо с предопределенными, особенно встроенными, функциями, просто никогда не использую их, еслиlambda
потребуется), но GvR в основном называл их не Pythonic в течение многих лет.itertools
? Часть, которую я цитирую из этого ответа, является основным утверждением, которое смущает меня. Я не знаю, находится ли в его идеальном миреmap
иfilter
будет ли двигатьсяitertools
(илиfunctools
) или идти целиком, но в зависимости от того, вitertools
каком случае это происходит, как только кто-то говорит, что он не является пифоническим, тогда я действительно не знаю, что такое «питон» должно означать, но я не думаю, что это может быть что-то похожее на «то, что GvR рекомендует людям использовать».map
/filter
, нетitertools
. Функциональное программирование прекрасно Pythonic (itertools
,functools
иoperator
были разработаны специально с функциональным программированием в виду, и я использую функциональные идиомы в Python все время), иitertools
предоставляет возможности , которые были бы боль , чтобы реализовать себя, Это конкретноmap
иfilter
быть избыточными с выражениями генератора это заставило Гвидо ненавидеть их.itertools
всегда было хорошо.Python 2: Вы должны использовать
map
иfilter
вместо списков.Цель причина , почему вы должны предпочесть их , даже если они не являются «Pythonic» заключается в следующем:
они требуют функции / лямбды в качестве аргументов, которые ввести новую область .
Я был укушен этим не раз:
но если бы вместо этого я сказал:
тогда все было бы хорошо.
Можно сказать, что я глуп, что использовал одно и то же имя переменной в той же области видимости.
Я не был Изначально код был в порядке -
x
они не были в одной области видимости.Только после того, как я переместил внутренний блок в другой раздел кода, возникла проблема (читай: проблема во время обслуживания, а не разработки), и я этого не ожидал.
Да, если вы никогда не совершите эту ошибку, тогда списочные представления будут более элегантными.
Но по личному опыту (и по тому, как другие видят ту же ошибку), я видел, что такое случалось достаточно много раз, и я думаю, что это не стоит той боли, которую вам придется пережить, когда эти ошибки проникают в ваш код.
Вывод:
Используйте
map
иfilter
. Они предотвращают тонкие, трудно диагностируемые ошибки, связанные с областью действия.Примечание:
Не забудьте рассмотреть возможность использования
imap
иifilter
(вitertools
), если они подходят для вашей ситуации!источник
map
и / илиfilter
. Во всяком случае, самый прямой и логичный перевод, чтобы избежать вашей проблемы, это неmap(lambda x: x ** 2, numbers)
выражение генератора,list(x ** 2 for x in numbers)
которое не просачивается, как уже указывал JeromeJ. Послушай, Мердад, не воспринимай это лично так глубоко, я просто не согласен с твоими рассуждениями.На самом деле,
map
и понимание списка ведет себя совсем по-другому в языке Python 3. Взгляните на следующую программу Python 3:Вы можете ожидать, что он напечатает строку «[1, 4, 9]» дважды, но вместо этого он напечатает «[1, 4, 9]», а затем «[]». Первый раз, когда вы смотрите на
squares
него, он выглядит как последовательность из трех элементов, но во второй раз как пустой.В языке Python 2
map
возвращается простой старый список, точно так же, как в обоих языках. Суть в том, что возвращаемое значениеmap
в Python 3 (иimap
в Python 2) не является списком - это итератор!Элементы потребляются, когда вы перебираете итератор в отличие от того, когда вы перебираете список. Вот почему
squares
выглядит пустым в последнейprint(list(squares))
строке.Обобщить:
источник
map
создать структуру данных, а не итератор. Но, может быть, ленивые итераторы проще, чем ленивые структуры данных. Пища для размышлений. Спасибо @MnZrKЯ нахожу, что понимание списков, как правило, более выразительно в отношении того, что я пытаюсь сделать, чем
map
- они оба делают это, но первое спасает умственную нагрузку от попыток понять, что может быть сложнымlambda
выражением.Также есть где-то интервью (я не могу его найти случайно), где Гвидо перечисляет
lambda
s и функциональные функции как то, что он больше всего сожалеет о принятии в Python, так что вы можете сделать аргумент, что они непиратонны в силу того, что.источник
const
ключевое слово в C ++ является большим триумфом в этом направлении.lambda
, они были сделаны настолько неубедительными (без заявлений ...), что их сложно использовать и в любом случае они ограничены.Вот один из возможных случаев:
против:
Я предполагаю, что zip () - это печальные и ненужные накладные расходы, которые нужно потворствовать, если вы настаиваете на использовании списочных представлений вместо карты. Было бы замечательно, если бы кто-то разъяснил это положительно или отрицательно.
источник
zip
ленивым, используяitertools.izip
map(operator.mul, list1, list2)
. Именно на этих очень простых выражениях в левой части понимание становится неуклюжим.Если вы планируете писать какой-либо асинхронный, параллельный или распределенный код, вы, вероятно, предпочтете
map
понимание списка - поскольку большинство асинхронных, параллельных или распределенных пакетов предоставляютmap
функцию для перегрузки Pythonmap
. Затем, передав соответствующуюmap
функцию остальной части вашего кода, вам, возможно, не придется изменять исходный последовательный код, чтобы он работал параллельно (и т. Д.).источник
Так как Python 3
map()
является итератором, вам нужно помнить, что вам нужно: итератор илиlist
объект.Как уже упоминалось @AlexMartelli ,
map()
это быстрее, чем понимание списка, только если вы не используетеlambda
функцию.Я представлю вам некоторые сравнения времени.
Python 3.5.2 и CPython
Я использовал ноутбук Jupiter и особенно
%timeit
встроенную магическую команду.Измерения : s == 1000 мс == 1000 * 1000 мкс = 1000 * 1000 * 1000 нс.
Настроить:
Встроенная функция:
lambda
функция:Существует также такая вещь, как выражение генератора, см. PEP-0289 . Поэтому я подумал, что было бы полезно добавить его в сравнение.
Вам нужен
list
объект:Используйте понимание списка, если это пользовательская функция, используйте,
list(map())
если есть встроенная функцияВам не нужен
list
объект, вам нужен только повторяемый:Всегда используйте
map()
!источник
Я провел быстрый тест, сравнивая три метода для вызова метода объекта. Разница во времени в этом случае незначительна и зависит от рассматриваемой функции (см. Ответ @Alex Martelli ). Здесь я посмотрел на следующие методы:
Я просмотрел списки (хранящиеся в переменной
vals
) как целых чисел (Pythonint
), так и чисел с плавающей запятой (Pythonfloat
) для увеличения размеров списков. Следующий фиктивный классDummyNum
считается:Конкретно
add
метод.__slots__
Атрибут является простой оптимизацией в Python , чтобы определить общий объем памяти , необходимый класс (атрибуты), уменьшая размер памяти. Вот итоговые сюжеты.Как указывалось ранее, используемая техника имеет минимальное значение, и вы должны кодировать ее наиболее удобным для вас способом или в определенных обстоятельствах. В этом случае понимание списка (
map_comprehension
техника) является самым быстрым для обоих типов дополнений в объекте, особенно с более короткими списками.Посетите этот каталог для источника, использованного для создания графика и данных.
источник
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()
основном имеют одинаковую производительность).list()
вызовы немного медленнее, чем списки. Для честного сравнения нужно написать[*map(...)]
.list()
звонки увеличились накладные расходы. Должен был потратить больше времени на чтение ответов. Я проведу эти тесты повторно для достоверного сравнения, однако различия могут быть незначительными.Я попробовал код @ alex-martelli, но обнаружил некоторые несоответствия
map занимает столько же времени даже для очень больших диапазонов, в то время как использование списков занимает много времени, как видно из моего кода. Таким образом, помимо того, что меня считают «непифоничным», я не сталкивался с проблемами производительности, связанными с использованием карты.
источник
map
возвращается список. В Python 3map
лениво оценивается, поэтому простой вызовmap
не вычисляет ни один из новых элементов списка, поэтому вы получаете такие короткие промежутки времени.Я считаю, что самый Pythonic способ состоит в том, чтобы использовать понимание списка вместо
map
иfilter
. Причина в том, что списки понятнее, чемmap
иfilter
.Как видите, понимание не требует дополнительных
lambda
выражений по мереmap
необходимости. Кроме того, понимание также позволяет легко фильтровать, в то время какmap
требуетfilter
разрешить фильтрацию.источник