map() oder list comprehension

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

Kennt jemand außer persönlicher Preferenzen, oder Lesbarkeit einen Grund, sich für den einen oder anderen Ausdruck zu entscheiden:

Code: Alles auswählen

import operator

a_values = [10, 11, 12, 13]
b_values = [1, 2, 3, 4]

# map
differences = list(map(operator.sub, a_values, b_values))

# list comprehension
differences = [a - b for a, b in zip(a_values, b_values)]
Benutzeravatar
__blackjack__
User
Beiträge: 14066
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

`map()` ist kürzer wenn man feststellt das man auch mit einem iterierbaren Objekt auskommt und nicht zwingend eine Liste braucht.

Edit: Und wenn der Operator austauschbar sein soll, dann ist die `map()`-Variante auch praktischer.

Edit2: Und wenn der Operator komplizierter als `operator.sub()` ist, könnte man den als Funktion geschrieben einfacher separat testen.

Ich weiss halt nicht wie allgemein Du diese Frage meinst, oder ob es jetzt wirklich *konkret* um diesen speziellen Fall geht, oder allgemeiner `map()` vs,. LC.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
noisefloor
User
Beiträge: 4195
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

die Python-Doku sagt dazu "(list comprehension) ... which is more concise and readable.".

Ansonsten sehe ich es auch wie __blackjack__. Die Antwort auf die Frage ist "Kommt drauf an".

Gruß, noisefloor
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

Es ging schon um diesen konkreten Fall.
Das "operator.sub()" hatte ich irgendwo gelesen und hatte mich gewundert, da man dafür ja extra "operator" importieren muss. Deswegen hatte ich mich gefragt ob es einen guten Grund dafür gibt.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Na der ist eben für Fälle, wo zb ein callback definiert wird. Klar kann man den auch trivial mit einem lambda ersetzen, aber so ist es etwas klarer.
Benutzeravatar
snafu
User
Beiträge: 6872
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich würde die LC nehmen, da lesbarer. Die mögliche Austauschbarkeit ist erstmal ein theoretisches Konstrukt, welches man wahrscheinlich in 99% der Fälle nicht benötigt. Ist in etwa so wie print(" ".join(map(str, items))) grundsätzlich gegenüber einem print(*items) zu bevorzugen, weil beim ersten die Bildschirmausgabe leichter weg gelassen werden kann. Klingt komisch, fiel hier aber tatsächlich vor kurzem und war wohl ernst gemeint. 🙉
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Eine Sache die man vielleicht bedenken sollte ist dass ein `a - b` wesentlich schneller ist als ein `operator.sub(a, b)` da bei letzterem der Overhead des Funktionsaufrufs hinzukommt und dürfte nochmal relevanter werden wenn man eine Funktion hat die in Python (statt C) implementiert ist

Das wird allerdings erst wirklich auffallen wenn der Code sehr häufig ausgeführt wird. Trotzdem würde ich `map()` vermeiden wenn es keinen Grund gibt eine Funktion einzuführen außer dass `map()` dies erfordert.
Benutzeravatar
__blackjack__
User
Beiträge: 14066
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@snafu: Ja das meinte ich ernst. Wenn man das `print()` durch Logging ersetzen will oder eine andere Funktion verwenden will, die Ausgabe in einer GUI angezeigt werden soll, usw. dann hat man in der Regelk nicht die Schnittstelle von `print()` die das ermöglicht.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
narpfel
User
Beiträge: 691
Registriert: Freitag 20. Oktober 2017, 16:10

@DasIch: Interessanterweise ist `map` trotzdem schneller (vermutlich weil der Overhead von `zip` wegfällt?):

CPython:

Code: Alles auswählen

In [1]: from operator import sub

In [2]: xs = list(range(1000))

In [3]: ys = list(range(1000))

In [4]: %timeit list(map(sub, xs, ys))
54.5 µs ± 104 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [5]: %timeit [x - y for x, y in zip(xs, ys)]
88.5 µs ± 143 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
PyPy:

Code: Alles auswählen

In [1]: from operator import sub

In [2]: xs = list(range(1000))

In [3]: ys = list(range(1000))

In [4]: %timeit list(map(sub, xs, ys))
7.98 µs ± 34.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [5]: %timeit [x - y for x, y in zip(xs, ys)]
8.58 µs ± 19.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Ich würde `map` allerdings auch nur dann benutzen, wenn ich schon eine fertige Funktion habe. `"".join(map(str, values))` ist das einzige Beispiel, das mir spontan einfallen würde.
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

map() ist aber nicht immer so schnell:

Code: Alles auswählen

In [4]: %timeit list(map(lambda x, y: x-y, xs, ys))
73.6 µs ± 185 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Ist ja auch irgendwie zu erwarten. Und das ist die hässlichste Variante
Benutzeravatar
__blackjack__
User
Beiträge: 14066
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@narpfel: Also ich benutze `map()` öfter mal. Ich denke gerne in ”Datenströmen”, also Iteratoren/Generatoren um welche zu erstellen, `map()` zum transformieren, `filter()` zum selektieren, `functools.reduce()` zum aggregieren.

Klar kann man `map()` und/oder `filter()` für einfachere Sachen auch als Generatorausdruck oder „list comprehension“ schreiben. Mache ich auch oft. Aber wenn erstellen eines transformierten Wertes oder die Prüfung beim selektieren komplexer sind, schreibe ich da gerne eine Funktion oder Methode für, die sich dann auch gleich (Unit-)Testen lässt. Oder eben wenn es bereits eine passende Funktion gibt.

Und ein Gegenstück zum `map()`, `str()`, `join()` was ich auch öfter verwende ist `split()`, `map()`, `convert_func()`. So etwas wie `numbers = list(map(int, text.split()))`` beispielsweise. Eventuell ohne das `list()` falls ein Iterator ausreicht für die Weiterverarbeitung.

Weitere Beispiele: ``instructions = map(Instruction.parse, sys.stdin)``, ``print(sum(map(evaluate, lines)))``, ``x, y = map(int, coordinate_part.split(',', 1))``, ``policies_and_passwords = list(map(parse_line, sys.stdin))``, ``return map(parse_group, split_at(lines, is_empty_line))``.

Man kann es auch mit LCs kombinieren: ``dimensions = [map(int, line.split('x')) for line in lines]``.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Antworten