Warum gibt map in Python 3 ein Kartenobjekt anstelle einer Liste zurück?

83

Ich bin daran interessiert, das neue Sprachdesign von Python 3.x zu verstehen .

In Python 2.7 gefällt mir die Funktion map:

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

In Python 3.x haben sich jedoch folgende Dinge geändert:

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

Ich verstehe das Wie, aber ich konnte keinen Hinweis auf das Warum finden. Warum haben die Sprachdesigner diese Wahl getroffen, was meiner Meinung nach sehr schmerzhaft ist? War dies ein Problem für Entwickler, sich an das Listenverständnis zu halten?

IMO, Liste kann natürlich als Functors gedacht werden ; und ich wurde irgendwie gedacht, um so zu denken:

fmap :: (a -> b) -> f a -> f b
NoIdeaHowToFixThis
quelle
2
Das Grundprinzip sollte das gleiche sein, warum wir Generatoren anstelle von Listenverständnissen verwenden. Durch die Verwendung von Lazy Evaluation müssen wir keine großen Dinge im Gedächtnis behalten. Überprüfen Sie die akzeptierte Antwort hier: stackoverflow.com/questions/1303347/…
Moberg
8
Könnten Sie erklären, warum dies "viel Schmerz" verursacht?
RemcoGerlich
3
Ich denke, das liegt daran, dass jahrelange Nutzung gezeigt hat, dass die meisten Verwendungen mapeinfach über das Ergebnis iteriert wurden. Das Erstellen einer Liste, wenn Sie sie nicht benötigen, ist ineffizient, daher haben die Entwickler beschlossen, mapfaul zu sein. Hier gibt es viel zu gewinnen für die Leistung und nicht viel zu verlieren (Wenn Sie eine Liste benötigen, fragen Sie einfach nach einer ... list(map(...))).
mgilson
3
Ok, ich finde es interessant, dass sie, anstatt das Functor-Muster beizubehalten und eine faule Version von List anzubieten, irgendwie beschlossen haben, eine faule Auswertung einer Liste zu erzwingen, wenn sie zugeordnet wird. Ich hätte es vorgezogen, das Recht zu haben, meine eigene Wahl zu treffen, auch bekannt als Generator -> Karte -> Generator oder Liste -> Karte -> Liste (bis zu meiner Entscheidung)
NoIdeaHowToFixThis
4
@NoIdeaHowToFixThis, eigentlich liegt es an Ihnen, wenn Sie die gesamte Liste benötigen, verwandeln Sie sie einfach in eine Liste, einfach wie die Hölle
Netwave

Antworten:

37

Ich denke , der Grund , warum Karte noch existiert überhaupt , wenn Generator Ausdrücke auch vorhanden ist , ist , dass es mehrere Iterator Argumente annehmen kann , dass alle geschlungen über und ging in der Funktion sind:

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

Das ist etwas einfacher als die Verwendung von zip:

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

Andernfalls wird einfach nichts über Generatorausdrücke hinzugefügt.

RemcoGerlich
quelle
1
Ich denke, wenn wir den Wunsch hinzufügen, zu betonen, dass Listenverständnisse pythonischer sind und die Sprachdesigner dies betonen wollten, ist dies meiner Meinung nach die Antwort vor Ort. @vishes_shell konzentriert sich irgendwie nicht genug auf das Sprachdesign.
NoIdeaHowToFixThis
2
Erzeugt in Python 2 und 3 unterschiedliche Ergebnisse, wenn die beiden Listen nicht gleich lang sind . Versuchen Sie es c = list(map(max, [1,2,3,4], [0,10,0,10, 99]))in Python 2 und in Python 3.
Cdarke
1
Hier ist eine Referenz für den ursprünglichen Plan, die Karte vollständig aus Python3 zu entfernen: artima.com/weblogs/viewpost.jsp?thread=98196
Bernhard
Hmm wie seltsam, wenn ich eine Karte in eine Liste einbinde, bekomme ich eine Liste mit 1 Elementlisten.
Awiebe
24

Da ein Iterator zurückgegeben wird, wird das Speichern der Liste in voller Größe im Speicher weggelassen. Damit Sie in Zukunft problemlos darüber iterieren können, ohne das Gedächtnis zu belasten. Möglicherweise benötigen Sie nicht einmal eine vollständige Liste, sondern den Teil davon, bis Ihr Zustand erreicht ist.

Sie können diese Dokumente nützlich finden, Iteratoren sind fantastisch.

Ein Objekt, das einen Datenstrom darstellt. Wiederholte Aufrufe der Iteratormethode __next__()(oder die Übergabe an die integrierte Funktion next()) geben aufeinanderfolgende Elemente im Stream zurück. Wenn keine Daten mehr verfügbar sind, StopIterationwird stattdessen eine Ausnahme ausgelöst. Zu diesem Zeitpunkt ist das Iteratorobjekt erschöpft und alle weiteren Aufrufe seiner __next__()Methode werden einfach StopIterationerneut ausgelöst . Iteratoren müssen über eine __iter__()Methode verfügen , die das Iteratorobjekt selbst zurückgibt, damit jeder Iterator auch iterierbar ist und an den meisten Stellen verwendet werden kann, an denen andere Iterablen akzeptiert werden. Eine bemerkenswerte Ausnahme ist Code, der mehrere Iterationsdurchläufe versucht. Ein Containerobjekt (z. B. a list) erzeugt jedes Mal, wenn Sie es an das übergeben, einen neuen Iteratoriter()Funktion oder verwenden Sie es in einer for-Schleife. Wenn Sie dies mit einem Iterator versuchen, wird nur dasselbe erschöpfte Iteratorobjekt zurückgegeben, das im vorherigen Iterationsdurchlauf verwendet wurde, sodass es wie ein leerer Container erscheint.

vishes_shell
quelle
14

Guido beantwortet diese Frage hier : " Da das Erstellen einer Liste nur verschwenderisch wäre ".

Er sagt auch, dass die richtige Transformation darin besteht, eine reguläre forSchleife zu verwenden.

Das Konvertieren map()von 2 in 3 ist möglicherweise nicht nur ein einfacher Fall, bei dem ein Wert darauf geklebt list( )wird. Guido sagt auch:

"Wenn die Eingabesequenzen nicht gleich lang sind, map()wird sie am Ende der kürzesten der Sequenzen beendet. Um die vollständige Kompatibilität mit map()Python 2.x zu gewährleisten , wickeln Sie die Sequenzen auch in itertools.zip_longest()z

map(func, *sequences)

wird

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

""

cdarke
quelle
3
Der Guido-Kommentar wird map()wegen der Nebenwirkungen der Funktion aufgerufen , nicht wegen ihrer Verwendung als Funktor.
Abukaj
4
Die Transformation mit zip_longestist falsch. Sie müssen verwenden, itertools.starmapdamit es gleichwertig ist : list(starmap(func, zip_longest(*sequences))). Das ist , weil zip_longestTupel erzeugt, so dass die funcein einziges erhalten würde n-uple Argument statt nunterschiedlicher Argumente wie es der Fall bei einem Anruf map(func, *sequences).
Bakuriu
12

In Python 3 geben viele Funktionen (nicht nur mapaber zip, sondern auch rangeandere) einen Iterator anstelle der vollständigen Liste zurück. Möglicherweise möchten Sie einen Iterator (z. B. um zu vermeiden, dass die gesamte Liste im Speicher bleibt) oder eine Liste (z. B. um indizieren zu können).

Ich denke jedoch, dass der Hauptgrund für die Änderung in Python 3 darin besteht, dass das Konvertieren eines Iterators in eine Liste mit list(some_iterator)dem umgekehrten Äquivalent iter(some_list)zwar trivial ist, aber nicht das gewünschte Ergebnis erzielt, da die vollständige Liste bereits erstellt und im Speicher gespeichert wurde.

In Python 3 list(range(n))funktioniert dies beispielsweise einwandfrei, da das rangeErstellen und anschließende Konvertieren des Objekts in eine Liste nur geringe Kosten verursacht . In Python 2 wird iter(range(n))jedoch kein Speicher gespeichert, da die vollständige Liste erstellt wird, range()bevor der Iterator erstellt wird.

Daher sind in Python 2 separate Funktionen erforderlich, um einen Iterator anstelle einer Liste zu erstellen, z. B. imapfor map(obwohl sie nicht ganz gleichwertig sind ), xrangefor range, izipfor zip. Im Gegensatz dazu benötigt Python 3 nur eine einzige Funktion, da ein list()Aufruf bei Bedarf die vollständige Liste erstellt.

Chris_Rands
quelle
AFAIK in Python 2.7 funktioniert auch von itertoolsRückkehriteratoren. Außerdem würde ich Iteratoren nicht als Lazy Lists betrachten, da Listen mehrfach iteriert und zufällig aufgerufen werden können.
Abukaj
@abukaj ok danke, ich habe meine Antwort bearbeitet, um klarer zu sein
Chris_Rands
@IgorRivin was meinst du? Python 3- mapObjekte haben eine next()Methode. Python 3 rangeRange Objekte sind keine strengen Iteratoren, die ich kenne
Chris_Rands
@Chris_Rands in meinem Anaconda-Distributionspython 3.6.2 gibt dabei foo = map(lambda x: x, [1, 2, 3])ein Kartenobjekt zurück foo. Tun foo.next()kommt mit einem Fehler zurück:'map' object has no attribute 'next'
Igor Rivin
1
@IgorRivin: Methoden, die mit beginnen und enden, __sind Python vorbehalten. Ohne diesen Vorbehalt haben Sie das Problem, Dinge zu unterscheiden, für die nextes sich nur um eine Methode handelt (sie sind nicht wirklich Iteratoren), und Dinge, die Iteratoren sind. In der Praxis sollten Sie die Methoden überspringen und nur die next()Funktion (z. B. next(foo)) verwenden, die ab 2.6 in jeder Python-Version ordnungsgemäß funktioniert. Es ist die gleiche Art und Weise, wie Sie es verwenden len(foo), obwohl foo.__len__()es gut funktionieren würde. Die Dunder-Methoden sollen im Allgemeinen nicht direkt, sondern implizit als Teil einer anderen Operation aufgerufen werden.
ShadowRanger