Почему карта возвращает объект карты вместо списка в Python 3?

83

Я заинтересован в понимании нового дизайна языка в Python 3.x .

Мне нравится в Python 2.7 функция map:

Python 2.7.12
In[2]: map(lambda x: x+1, [1,2,3])
Out[2]: [2, 3, 4]

Однако в Python 3.x все изменилось:

Python 3.5.1
In[2]: map(lambda x: x+1, [1,2,3])
Out[2]: <map at 0x4218390>

Я понимаю как, но я не мог найти ссылку на почему. Почему разработчики языка сделали этот выбор, который, на мой взгляд, приносит много боли. Было ли это для того, чтобы разработчики пытались заставить разработчиков придерживаться списка понятий?

ИМО, список естественно рассматривать как Функторы ; и меня почему-то думали так:

fmap :: (a -> b) -> f a -> f b
NoIdeaHowToFixThis
источник
2
Обоснование должно быть таким же, как то, почему мы используем генераторы вместо составления списков. Используя ленивое вычисление, нам не нужно хранить в памяти огромные вещи. Проверьте принятый ответ здесь: stackoverflow.com/questions/1303347/…
Moberg
8
Не могли бы вы объяснить, почему это приносит вам «сильную боль»?
RemcoGerlich
3
Я думаю, это потому, что годы использования показали, что в большинстве случаев mapпросто повторяется результат. Создание списка, когда он вам не нужен, неэффективно, поэтому разработчики решили mapполениться. Здесь можно многое получить для повышения производительности и не так много потерять (если вам нужен список, просто попросите его ... list(map(...))).
mgilson
3
Хорошо, мне интересно, что вместо того, чтобы сохранить шаблон Functor и предложить ленивую версию List, они каким-то образом приняли решение принудительно выполнять ленивую оценку списка всякий раз, когда он отображается. Я бы предпочел иметь право делать свой собственный выбор, он же Генератор -> Карта -> Генератор или Список -> Карта -> Список (решать мне)
NoIdeaHowToFixThis
4
@NoIdeaHowToFix Это, на самом деле, зависит от вас, если вам нужен весь список, просто преобразуйте его в список, чертовски просто
Netwave

Ответы:

37

Я думаю , что причина , почему карта до сих пор существует вообще , когда выражение генератора также существует, в том , что он может принимать несколько аргументов итератора, которые все накинет и передаются в функцию:

>>> list(map(min, [1,2,3,4], [0,10,0,10]))
[0,2,0,4]

Это немного проще, чем использовать zip:

>>> list(min(x, y) for x, y in zip([1,2,3,4], [0,10,0,10]))

В противном случае он просто ничего не добавляет к выражениям генератора.

РемкоГерлих
источник
1
Я думаю, что если мы добавим желание подчеркнуть, что понимание списков является более питоническим, и разработчики языка хотели подчеркнуть это, я думаю, это будет наиболее точный ответ. @vishes_shell почему-то недостаточно сосредоточен на языковом дизайне.
NoIdeaHowToFixThis
2
Дает разные результаты в Python 2 и 3, если два списка имеют разную длину . Попробуйте c = list(map(max, [1,2,3,4], [0,10,0,10, 99]))в Python 2 и в Python 3.
cdarke
1
Вот ссылка на исходный план полного удаления карты из python3: artima.com/weblogs/viewpost.jsp?thread=98196
Бернхард,
Хм, как странно, когда я оборачиваю карту в список, я получаю список из 1 списка элементов.
awiebe
24

Поскольку он возвращает итератор, он не сохраняет полный список в памяти. Так что вы можете легко перебирать его в будущем, не причиняя боли памяти. Возможно, вам нужен даже не полный список, а его часть, пока ваше состояние не будет достигнуто.

Вы можете найти эту документацию полезной, итераторы потрясающие.

Объект, представляющий поток данных. Повторные вызовы __next__()метода итератора (или передача его встроенной функции next()) возвращают последовательные элементы в потоке. Когда больше нет доступных данных, StopIterationвместо этого возникает исключение. На этом этапе объект-итератор исчерпан, и любые дальнейшие вызовы его __next__()метода просто вызываются StopIterationснова. Итераторы должны иметь __iter__()метод, который возвращает сам объект итератора, поэтому каждый итератор также является итеративным и может использоваться в большинстве мест, где допускаются другие итераторы. Одно примечательное исключение - это код, который пытается выполнить несколько итераций. Объект-контейнер (например, a list) создает новый итератор каждый раз, когда вы передаете его вiter()функцию или используйте ее в цикле for. Попытка сделать это с помощью итератора просто вернет тот же исчерпанный объект итератора, который использовался в предыдущем проходе итерации, что сделает его похожим на пустой контейнер.

vishes_shell
источник
14

Гвидо отвечает на этот вопрос здесь : « поскольку создание списка было бы расточительным ».

Он также говорит, что правильное преобразование - использовать обычный forцикл.

Преобразование map()из 2 в 3 может быть не простым случаем наклеивания list( )вокруг него. Гвидо также говорит:

"Если входные последовательности не равной длины, map()остановится на завершении самой короткой из последовательностей. Для полной совместимости с map()Python 2.x также оберните последовательности itertools.zip_longest(), например

map(func, *sequences)

становится

list(map(func, itertools.zip_longest(*sequences)))

"

cdarke
источник
3
Комментарий Guido map()вызывается для побочных эффектов функции , а не для ее использования в качестве функтора.
abukaj
4
Преобразование с zip_longestневерно. вы должны использовать itertools.starmapдля того , чтобы быть эквивалентным: list(starmap(func, zip_longest(*sequences))). Это потому, что zip_longestсоздает кортежи, поэтому вместо отдельных аргументов, как в случае вызова, funcбудет nполучен единственный аргумент -uple . nmap(func, *sequences)
Bakuriu
12

В Python 3 много функций (не только , mapно zip, rangeи другие) возвращает итератор , а не полный список. Вам может понадобиться итератор (например, чтобы не хранить весь список в памяти) или список (например, чтобы иметь возможность индексировать).

Однако я думаю, что основная причина изменения в Python 3 заключается в том, что, хотя преобразование итератора в список с использованием list(some_iterator)обратного эквивалента тривиально, iter(some_list)не приводит к желаемому результату, поскольку полный список уже создан и хранится в памяти.

Например, в Python 3 все list(range(n))работает нормально, поскольку создание rangeобъекта и его последующее преобразование в список не требуют больших затрат . Однако в Python 2 iter(range(n))память не сохраняется, поскольку полный список создается range()до построения итератора.

Следовательно, в Python 2 для создания итератора требуются отдельные функции, а не список, например imapfor map(хотя они не совсем эквивалентны ), xrangefor range, izipfor zip. В отличие от этого Python 3 требует только одной функции, поскольку при list()вызове создается полный список, если это необходимо.

Chris_Rands
источник
AFAIK в Python 2.7 также работает с itertoolsитераторами возврата. Кроме того, я бы не рассматривал итераторы как ленивые списки, поскольку списки можно повторять несколько раз и обращаться к ним случайным образом.
abukaj
@abukaj, хорошо, спасибо, я отредактировал свой ответ, чтобы было понятнее
Chris_Rands
@IgorRivin, что ты имеешь в виду? У mapобъектов Python 3 есть next()метод. rangeЯ знаю, что объекты диапазона Python 3 не являются строго итераторами
Chris_Rands
@Chris_Rands в моем Python 3.6.2 распространения Anaconda, выполнение foo = map(lambda x: x, [1, 2, 3])возвращает объект карты foo. выполнение foo.next()возвращается с ошибкой:'map' object has no attribute 'next'
Игорь Ривин
1
@IgorRivin: методы, начинающиеся и заканчивающиеся на __, зарезервированы для Python; без этой оговорки у вас возникнет проблема с различением вещей, для которых nextявляется просто методом (на самом деле они не итераторы), и вещей, которые являются итераторами. На практике вам следует пропустить методы и просто использовать next()функцию (например next(foo)), которая правильно работает на каждой версии Python, начиная с 2.6. Это тот же самый способ, которым вы пользуетесь, len(foo)хотя он foo.__len__()будет работать нормально; методы dunder обычно предназначены для вызова не напрямую, а неявно как часть какой-либо другой операции.
ShadowRanger