Есть ли причина, по которой вы предпочитаете использовать map()
списки или наоборот? Является ли один из них более эффективным или считается более питоническим, чем другой?
Есть ли причина, по которой вы предпочитаете использовать map()
списки или наоборот? Является ли один из них более эффективным или считается более питоническим, чем другой?
Ответы:
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
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)
, который является однострочным, который очень приблизительно эквивалентен: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
класс немного непрозрачен для разборки, но мы можем сделать это с нашим тестом скорости.
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
всегда было хорошо.
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
(в itertools
), если они подходят для вашей ситуации!
map
и / или filter
. Во всяком случае, самый прямой и логичный перевод, чтобы избежать вашей проблемы, это не map(lambda x: x ** 2, numbers)
выражение генератора, list(x ** 2 for x in numbers)
которое не просачивается, как уже указывал JeromeJ. Послушай, Мердад, не воспринимай это лично так глубоко, я просто не согласен с твоими рассуждениями.
На самом деле, 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))
строке.
Обобщить:
map
создать структуру данных, а не итератор. Но, может быть, ленивые итераторы проще, чем ленивые структуры данных. Пища для размышлений. Спасибо @MnZrK
Я нахожу, что понимание списков, как правило, более выразительно в отношении того, что я пытаюсь сделать, чем map
- они оба делают это, но первое спасает умственную нагрузку от попыток понять, что может быть сложным lambda
выражением.
Также есть где-то интервью (я не могу его найти случайно), где Гвидо перечисляет lambda
s и функциональные функции как то, что он больше всего сожалеет о принятии в Python, так что вы можете сделать аргумент, что они непиратонны в силу того, что.
const
ключевое слово в C ++ является большим триумфом в этом направлении.
lambda
, они были сделаны настолько неубедительными (без заявлений ...), что их сложно использовать и в любом случае они ограничены.
Вот один из возможных случаев:
map(lambda op1,op2: op1*op2, list1, list2)
против:
[op1*op2 for op1,op2 in zip(list1,list2)]
Я предполагаю, что zip () - это печальные и ненужные накладные расходы, которые нужно потворствовать, если вы настаиваете на использовании списочных представлений вместо карты. Было бы замечательно, если бы кто-то разъяснил это положительно или отрицательно.
zip
ленивым, используяitertools.izip
map(operator.mul, list1, list2)
. Именно на этих очень простых выражениях в левой части понимание становится неуклюжим.
Если вы планируете писать какой-либо асинхронный, параллельный или распределенный код, вы, вероятно, предпочтете map
понимание списка - поскольку большинство асинхронных, параллельных или распределенных пакетов предоставляют map
функцию для перегрузки Python map
. Затем, передав соответствующую map
функцию остальной части вашего кода, вам, возможно, не придется изменять исходный последовательный код, чтобы он работал параллельно (и т. Д.).
Так как 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()
!
Я провел быстрый тест, сравнивая три метода для вызова метода объекта. Разница во времени в этом случае незначительна и зависит от рассматриваемой функции (см. Ответ @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 , чтобы определить общий объем памяти , необходимый класс (атрибуты), уменьшая размер памяти. Вот итоговые сюжеты.
Как указывалось ранее, используемая техника имеет минимальное значение, и вы должны кодировать ее наиболее удобным для вас способом или в определенных обстоятельствах. В этом случае понимание списка ( 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, но обнаружил некоторые несоответствия
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 занимает столько же времени даже для очень больших диапазонов, в то время как использование списков занимает много времени, как видно из моего кода. Таким образом, помимо того, что меня считают «непифоничным», я не сталкивался с проблемами производительности, связанными с использованием карты.
map
возвращается список. В Python 3 map
лениво оценивается, поэтому простой вызов map
не вычисляет ни один из новых элементов списка, поэтому вы получаете такие короткие промежутки времени.
Я считаю, что самый 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
разрешить фильтрацию.