Чому map повертає об’єкт карти замість списку в 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

2
Обгрунтування повинно бути однаковим, чому ми використовуємо генератори замість розуміння списків. За допомогою ледачої оцінки нам не потрібно зберігати величезні речі в пам'яті. Перевірте прийняту відповідь тут: stackoverflow.com/questions/1303347/…
Moberg

8
Не могли б ви пояснити, чому це приносить вам "сильний біль"?
RemcoGerlich

3
Думаю, це тому, що роки використання показали, що найпоширеніші способи використання mapпросто повторюються над результатом. Складання списку, коли він вам не потрібен, є неефективним, тому розробники вирішили mapлінуватися. Тут можна багато здобути для продуктивності, а втратити не багато (якщо вам потрібен список, просто попросіть його ... list(map(...))).
mgilson

3
Гаразд, мені цікаво, що замість того, щоб зберегти шаблон Functor і запропонувати ледачу версію List, вони якось прийняли рішення змусити ліниву оцінку списку щоразу, коли він відображається. Я волів би мати право зробити свій власний вибір, він же Генератор -> карта -> Генератор або Список -> карта -> Список (вирішувати мені)
NoIdeaHowToFixThis

4
@NoIdeaHowToFixThis, насправді залежить від вас, якщо вам потрібен цілий список, просто перетворіть його в список, простий як біс
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__()метод, який повертає сам об'єкт ітератора, тому кожен ітератор також є ітерабельним і може використовуватися в більшості місць, де приймаються інші ітератори. Визначним винятком є ​​код, який намагається пройти кілька ітерацій. Об'єкт-контейнер (наприкладlist ) створює новий ітератор кожного разу, коли ви передаєте його вiter()функцію або використовувати її у циклі for. Спроба цього за допомогою ітератора просто поверне той самий вичерпаний об’єкт ітератора, який використовувався в попередньому ітераційному проході, роблячи його схожим на порожній контейнер.


14

Гвідо відповідає на це запитання тут : " оскільки створення списку було б просто марним ".

Він також каже, що правильним перетворенням є використання регулярного forциклу.

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

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

map(func, *sequences)

стає

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

"


3
Коментар Guido map()посилається на побічні ефекти функції , а не на використання її як функтора.
abukaj

4
Трансформація з zip_longestпомилковою. ви повинні використовувати itertools.starmapдля того , щоб бути еквівалентним: list(starmap(func, zip_longest(*sequences))). Це тому, що zip_longestвиробляє кортежі, тож отримає аргумент funcз однією nодиницею замість nокремих аргументів, як це відбувається під час виклику map(func, *sequences).
Бакуріу

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()виклик створює повний список, якщо потрібно.


AFAIK у Python 2.7 також функціонує з itertoolsітераторів повернення. Крім того, я не бачив би ітератори як ледачі списки, оскільки списки можна повторювати кілька разів і отримувати випадковий доступ до них.
abukaj

@abukaj добре, дякую, я відредагував свою відповідь, щоб спробувати бути зрозумілішим
Chris_Rands

@IgorRivin що ти маєш на увазі? mapОб'єкти Python 3 мають next()метод. rangeОб'єкти діапазону Python 3 не є строго ітераторами, яких я знаю
Chris_Rands

@Chris_Rands у моєму розподільчому пітоні Anaconda 3.6.2, 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.