Dlaczego map zwraca obiekt mapy zamiast listy w Pythonie 3?

83

Jestem zainteresowany w zrozumieniu nowej konstrukcji języka Python 3.x .

Podoba mi się funkcja w Pythonie 2.7 map:

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

Jednak w Pythonie 3.x sytuacja uległa zmianie:

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

Rozumiem jak, ale nie mogłem znaleźć odniesienia do przyczyny. Dlaczego projektanci języka dokonali takiego wyboru, który moim zdaniem wprowadza wiele bólu. Czy to było dla twórców siłowania się na rękę, którzy trzymali się listy zrozumień?

IMO, listę można oczywiście traktować jako funktory ; i jakoś sądzono, że myślę w ten sposób:

fmap :: (a -> b) -> f a -> f b
NoIdeaHowToFixThis
źródło
2
Powód powinien być taki sam, jak to, dlaczego używamy generatorów zamiast list składanych. Używając leniwej oceny, nie musimy trzymać w pamięci wielkich rzeczy. Sprawdź zaakceptowaną odpowiedź tutaj: stackoverflow.com/questions/1303347/…
Moberg
8
Czy mógłbyś wyjaśnić, dlaczego powoduje to „dużo bólu”?
RemcoGerlich
3
Myślę, że to dlatego, że lata użytkowania pokazały, że większość zwykłych zastosowań mappo prostu powtarzała się po wyniku. Tworzenie listy, gdy jej nie potrzebujesz, jest nieefektywne, więc twórcy postanowili się maprozlenić. Wiele można tu zyskać za wydajność, a nie wiele do stracenia (jeśli potrzebujesz listy, poproś o nią ... list(map(...))).
mgilson
3
Ok, wydaje mi się interesujące, że zamiast zachować wzorzec Functor i zaoferować leniwą wersję listy, w jakiś sposób podjęli decyzję o wymuszeniu leniwej oceny listy, gdy jest ona mapowana. Wolałbym mieć prawo do samodzielnego wyboru, czyli Generator -> mapa -> Generator lub Lista -> mapa -> Lista (do mnie należy decyzja)
NoIdeaHowToFix
4
@NoIdeaHowToFix To właściwie zależy od Ciebie, jeśli potrzebujesz całej listy, po prostu przekształć ją w listę, łatwo jak diabli
Netwave

Odpowiedzi:

37

Myślę, że jest to powód, dla którego mapa nadal istnieje w ogóle , gdy wyrażenia generatorów również istnieje, jest to, że może to potrwać wiele argumentów iteratorów, które są zapętlone nad i przekazywane do funkcji:

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

To trochę łatwiejsze niż używanie zip:

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

W przeciwnym razie po prostu nie dodaje niczego ponad wyrażeniami generatora.

RemcoGerlich
źródło
1
Myślę, że jeśli dodamy do tego chęć podkreślenia, że ​​rozumienie list jest bardziej pythonowe, a projektanci języka chcieli to podkreślić, to wydaje mi się, że jest to najbardziej na miejscu odpowiedź. @vishes_shell w jakiś sposób nie skupia się wystarczająco na projektowaniu języka.
NoIdeaHowToFix Ten
2
Daje różne wyniki w Pythonie 2 i 3, jeśli dwie listy nie są równej długości . Wypróbuj c = list(map(max, [1,2,3,4], [0,10,0,10, 99]))w Pythonie 2 i Pythonie 3.
cdarke
1
Oto odniesienie do pierwotnego planu całkowitego usunięcia mapy z pythona3: artima.com/weblogs/viewpost.jsp?thread=98196
Bernhard
Hmm, jakie dziwne, kiedy zawijam mapę w listę, otrzymuję listę 1 list elementów.
awiebe
24

Ponieważ zwraca iterator, pomija przechowywanie listy pełnego rozmiaru w pamięci. Abyś mógł z łatwością powtarzać to w przyszłości, nie powodując żadnego bólu w pamięci. Prawdopodobnie nie potrzebujesz nawet pełnej listy, ale jej części, aż do osiągnięcia stanu.

Możesz znaleźć te dokumenty przydatne, iteratory są niesamowite.

Obiekt reprezentujący strumień danych. Powtarzające się wywołania metody iteratora __next__()(lub przekazanie jej do funkcji wbudowanej next()) zwracają kolejne elementy w strumieniu. Gdy nie ma więcej dostępnych danych, StopIterationzamiast tego generowany jest wyjątek. W tym momencie obiekt iteratora jest wyczerpany i wszelkie dalsze wywołania jego __next__()metody po prostu podnoszą się StopIterationponownie. Iteratory muszą mieć __iter__()metodę, która zwraca sam obiekt iteratora, więc każdy iterator jest również iterowalny i może być używany w większości miejsc, w których akceptowane są inne iterowalne. Jedynym godnym uwagi wyjątkiem jest kod, który próbuje wielu przejść iteracji. Obiekt kontenera (taki jak a list) tworzy nowy nowy iterator za każdym razem, gdy przekazujesz go doiter()funkcji lub użyj go w pętli for. Próba wykonania tego za pomocą iteratora zwróci ten sam wyczerpany obiekt iteratora, który został użyty w poprzednim przebiegu iteracji, dzięki czemu będzie wyglądał jak pusty kontener.

vishes_shell
źródło
14

Guido odpowiada tutaj na to pytanie : „ ponieważ tworzenie listy byłoby po prostu marnotrawstwem ”.

Mówi też, że właściwą transformacją jest użycie regularnej forpętli.

Konwersja map()z 2 na 3 może nie być prostym przypadkiem naklejenia list( )wokół niej znaku . Guido mówi również:

„Jeśli sekwencje wejściowe nie są równej długości, map()zatrzyma się po zakończeniu najkrótszej z sekwencji. Aby uzyskać pełną zgodność z map()wersją z języka Python 2.x, należy również zawijać sekwencje itertools.zip_longest(), np.

map(func, *sequences)

staje się

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

"

cdarke
źródło
3
Komentarz Guido dotyczy map()skutków ubocznych funkcji , a nie jej użycia jako funktora.
abukaj
4
Transformacja z zip_longestjest zła. trzeba użyć itertools.starmap, aby była ona równoważne: list(starmap(func, zip_longest(*sequences))). Dzieje się zip_longesttak, ponieważ tworzy krotki, więc funcotrzyma pojedynczy nargument zamiast noddzielnych argumentów, jak ma to miejsce podczas wywoływania map(func, *sequences).
Bakuriu
12

W Pythonie 3 wiele funkcji (nie tylko mapale zip, rangei inni) zwraca iterator zamiast pełnej listy. Możesz potrzebować iteratora (np. Aby uniknąć trzymania całej listy w pamięci) lub możesz chcieć listy (np. Aby móc indeksować).

Jednak myślę, że głównym powodem zmiany w Pythonie 3 jest to, że chociaż konwersja iteratora na listę przy użyciu list(some_iterator)odwrotnego odpowiednika jest trywialna, iter(some_list)nie daje pożądanego rezultatu, ponieważ pełna lista została już zbudowana i przechowywana w pamięci.

Na przykład w Pythonie 3 list(range(n))działa dobrze, ponieważ zbudowanie rangeobiektu, a następnie przekonwertowanie go na listę, kosztuje niewiele . Jednak w Pythonie 2 iter(range(n))nie oszczędza pamięci, ponieważ pełna lista jest tworzona przez range()przed zbudowaniem iteratora.

Dlatego w Pythonie 2 do utworzenia iteratora wymagane są oddzielne funkcje, a nie lista, na przykład imapfor map(chociaż nie są one do końca równoważne ), xrangefor range, izipfor zip. Natomiast Python 3 wymaga tylko jednej funkcji, ponieważ list()wywołanie tworzy pełną listę, jeśli jest to wymagane.

Chris_Rands
źródło
AFAIK w Pythonie 2.7 również funkcje z itertoolsiteratorów powrotu. Ponadto nie postrzegałbym iteratorów jako leniwych list, ponieważ listy można iterować wiele razy i uzyskać do nich losowy dostęp.
abukaj
@abukaj ok dzięki, zredagowałem odpowiedź, aby była bardziej zrozumiała
Chris_Rands
@IgorRivin co masz na myśli? mapObiekty Pythona 3 mają next()metodę. rangeObiekty zakresu Pythona 3 nie są ściśle iteratorami, które znam
Chris_Rands
@Chris_Rands w moim Pythonie 3.6.2 dystrybucji Anaconda, wykonanie foo = map(lambda x: x, [1, 2, 3])zwraca obiekt mapy foo. działanie foo.next()wraca z błędem:'map' object has no attribute 'next'
Igor Rivin
1
@IgorRivin: Metody rozpoczynające się i kończące na __są zarezerwowane dla Pythona; bez tego zastrzeżenia, masz problem z rozróżnieniem rzeczy, które nextsą tylko metodą (nie są one tak naprawdę iteratorami) i rzeczy, które są iteratorami. W praktyce należy pominąć metody i po prostu skorzystać z next()funkcji (np. next(foo)), Która działa poprawnie na każdej wersji Pythona od 2.6. Jest to ten sam sposób, w jaki używasz, len(foo)chociaż foo.__len__()działałoby dobrze; że Dunder metody są zazwyczaj przeznaczone nie być wywoływane bezpośrednio, lecz pośrednio w ramach innej operacji.
ShadowRanger,