Gibt es einen Grund, die Verwendung von map()
über das Listenverständnis oder umgekehrt? Ist eine der beiden Methoden generell effizienter oder gilt sie als pythonischer als die andere?
Antworten
Zu viele Anzeigen?Ich finde, dass Listenzusammenfassungen im Allgemeinen besser ausdrücken, was ich zu tun versuche, als map
- beides ist möglich, aber Ersteres erspart die geistige Belastung, die mit dem Verständnis eines möglicherweise komplexen Themas verbunden ist. lambda
Ausdruck.
Es gibt auch irgendwo ein Interview (ich kann es nicht ausfindig machen), in dem Guido aufzählt lambda
s und die funktionalen Funktionen als die Sache, die er am meisten bedauert, in Python zu akzeptieren, so könnte man das Argument, dass sie un-Python durch die Kraft, dass zu machen.
Seit Python 3, map()
ein Iterator ist, müssen Sie sich überlegen, was Sie brauchen: einen Iterator oder list
Objekt.
Wie @AlexMartelli bereits erwähnt , map()
ist nur dann schneller als das Verstehen von Listen, wenn Sie nicht lambda
Funktion.
Ich werde Ihnen einige Zeitvergleiche vorstellen.
Python 3.5.2 und CPython<br>Ich habe die <a href="https://ipython.org/notebook.html" rel="noreferrer">Jupiter Notizbuch </a>und insbesondere <a href="https://ipython.org/ipython-doc/3/interactive/magics.html#magic-timeit" rel="noreferrer"><code>%timeit</code></a> eingebauter magischer Befehl<br><strong>Messungen </strong>s == 1000 ms == 1000 1000 µs = 1000 1000 * 1000 ns
Einrichten:
x_list = [(i, i+1, i+2, i*2, i-9) for i in range(1000)]
i_list = list(range(1000))
Eingebaute Funktion:
%timeit map(sum, x_list) # creating iterator object
# Output: The slowest run took 9.91 times longer than the fastest.
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 277 ns per loop
%timeit list(map(sum, x_list)) # creating list with map
# Output: 1000 loops, best of 3: 214 µs per loop
%timeit [sum(x) for x in x_list] # creating list with list comprehension
# Output: 1000 loops, best of 3: 290 µs per loop
lambda
Funktion:
%timeit map(lambda i: i+1, i_list)
# Output: The slowest run took 8.64 times longer than the fastest.
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 325 ns per loop
%timeit list(map(lambda i: i+1, i_list))
# Output: 1000 loops, best of 3: 183 µs per loop
%timeit [i+1 for i in i_list]
# Output: 10000 loops, best of 3: 84.2 µs per loop
Es gibt auch so etwas wie einen Generatorausdruck, siehe PEP-0289 . Also dachte ich, es wäre nützlich, es zum Vergleich hinzuzufügen
%timeit (sum(i) for i in x_list)
# Output: The slowest run took 6.66 times longer than the fastest.
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 495 ns per loop
%timeit list((sum(x) for x in x_list))
# Output: 1000 loops, best of 3: 319 µs per loop
%timeit (i+1 for i in i_list)
# Output: The slowest run took 6.83 times longer than the fastest.
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 506 ns per loop
%timeit list((i+1 for i in i_list))
# Output: 10000 loops, best of 3: 125 µs per loop
Sie benötigen list
Objekt:
Listenverständnis verwenden, wenn es sich um eine benutzerdefinierte Funktion handelt, verwenden list(map())
wenn es eine eingebaute Funktion gibt
Sie brauchen keine list
Objekt, Sie brauchen nur ein iterierbares:
Verwenden Sie immer map()
!
Ich habe einen kurzen Test durchgeführt, in dem ich drei Methoden zum Aufrufen der Methode eines Objekts verglichen habe. Der Zeitunterschied ist in diesem Fall vernachlässigbar und hängt von der jeweiligen Funktion ab (siehe @Alex Martelli's Antwort ). Hier habe ich mir die folgenden Methoden angesehen:
# map_lambda
list(map(lambda x: x.add(), vals))
# map_operator
from operator import methodcaller
list(map(methodcaller("add"), vals))
# map_comprehension
[x.add() for x in vals]
Ich habe mir Listen angesehen (gespeichert in der Variablen vals
) der beiden Ganzzahlen (Python int
) und Gleitkommazahlen (Python float
) für zunehmende Listengrößen. Die folgende Dummy-Klasse DummyNum
berücksichtigt wird:
class DummyNum(object):
"""Dummy class"""
__slots__ = 'n',
def __init__(self, n):
self.n = n
def add(self):
self.n += 5
Genauer gesagt, die add
Methode. Die Website __slots__
Attribut ist eine einfache Optimierung in Python, um den Gesamtspeicherbedarf der Klasse (Attribute) zu definieren und so die Speichergröße zu reduzieren. Hier sind die resultierenden Diagramme.
Wie bereits erwähnt, macht die verwendete Technik nur einen minimalen Unterschied, und Sie sollten den Code so schreiben, wie er für Sie oder unter den gegebenen Umständen am besten lesbar ist. In diesem Fall wird das Listenverständnis ( map_comprehension
Technik) ist für beide Arten von Hinzufügungen in einem Objekt am schnellsten, insbesondere bei kürzeren Listen.
Besuchen Sie dieses Pastebin für die Quelle, die zur Erstellung des Diagramms und der Daten verwendet wurde.
Ich habe einige der Ergebnisse mit Perfplot (ein Projekt von mir).
Wie andere bereits festgestellt haben, map
gibt eigentlich nur einen Iterator zurück, so dass es sich um eine Operation mit konstanter Zeit handelt. Bei der Realisierung des Iterators durch list()
ist sie gleichwertig mit den Listenauffassungen. Je nach Ausdruck könnte einer der beiden einen leichten Vorteil haben, aber der ist kaum signifikant.
Beachten Sie, dass arithmetische Operationen wie x ** 2
son viel schneller in NumPy, insbesondere wenn die Eingabedaten bereits ein NumPy-Array sind.
hex
:
x ** 2
:
Code zum Reproduzieren der Diagramme:
import perfplot
def standalone_map(data):
return map(hex, data)
def list_map(data):
return list(map(hex, data))
def comprehension(data):
return [hex(x) for x in data]
b = perfplot.bench(
setup=lambda n: list(range(n)),
kernels=[standalone_map, list_map, comprehension],
n_range=[2 ** k for k in range(20)],
equality_check=None,
)
b.save("out.png")
b.show()
import perfplot
import numpy as np
def standalone_map(data):
return map(lambda x: x ** 2, data[0])
def list_map(data):
return list(map(lambda x: x ** 2, data[0]))
def comprehension(data):
return [x ** 2 for x in data[0]]
def numpy_asarray(data):
return np.asarray(data[0]) ** 2
def numpy_direct(data):
return data[1] ** 2
b = perfplot.bench(
setup=lambda n: (list(range(n)), np.arange(n)),
kernels=[standalone_map, list_map, comprehension, numpy_direct, numpy_asarray],
n_range=[2 ** k for k in range(20)],
equality_check=None,
)
b.save("out2.png")
b.show()
Ich habe den Code von @alex-martelli ausprobiert, aber einige Unstimmigkeiten gefunden
python -mtimeit -s "xs=range(123456)" "map(hex, xs)"
1000000 loops, best of 5: 218 nsec per loop
python -mtimeit -s "xs=range(123456)" "[hex(x) for x in xs]"
10 loops, best of 5: 19.4 msec per loop
map benötigt selbst für sehr große Bereiche die gleiche Zeit, während die Verwendung von List Comprehension sehr viel Zeit in Anspruch nimmt, wie aus meinem Code ersichtlich ist. Abgesehen davon, dass ich als "unpythonisch" gelte, habe ich also keine Leistungsprobleme im Zusammenhang mit der Verwendung von map gehabt.