Ich habe folgendes Problem.
Bei einer Liste von Ganzzahlen
L
muss ich alle Unterlisten generierenL[k:]
for k in [0, len(L) - 1]
, ohne Kopien zu generieren .
Wie schaffe ich das in Python? Irgendwie mit einem Pufferobjekt?
Ich habe folgendes Problem.
Bei einer Liste von Ganzzahlen
L
muss ich alle Unterlisten generierenL[k:]
for k in [0, len(L) - 1]
, ohne Kopien zu generieren .
Wie schaffe ich das in Python? Irgendwie mit einem Pufferobjekt?
Antworten:
Durch das Schneiden von Listen werden keine Kopien der Objekte in der Liste generiert. es kopiert nur die Verweise auf sie. Das ist die Antwort auf die gestellte Frage.
Lassen Sie uns zunächst den Grundanspruch testen. Wir können zeigen, dass selbst bei unveränderlichen Objekten wie ganzen Zahlen nur die Referenz kopiert wird. Hier sind drei verschiedene ganzzahlige Objekte mit jeweils demselben Wert:
>>> a = [1000 + 1, 1000 + 1, 1000 + 1]
Sie haben den gleichen Wert, aber Sie können sehen, dass es sich um drei verschiedene Objekte handelt, da sie unterschiedliche id
s haben:
>>> map(id, a)
[140502922988976, 140502922988952, 140502922988928]
Wenn Sie sie in Scheiben schneiden, bleiben die Referenzen gleich. Es wurden keine neuen Objekte erstellt:
>>> b = a[1:3]
>>> map(id, b)
[140502922988952, 140502922988928]
Die Verwendung verschiedener Objekte mit demselben Wert zeigt, dass sich der Kopiervorgang nicht um das Internieren kümmert, sondern lediglich die Referenzen direkt kopiert.
Das Testen mit veränderlichen Werten ergibt das gleiche Ergebnis:
>>> a = [{0: 'zero', 1: 'one'}, ['foo', 'bar']]
>>> map(id, a)
[4380777000, 4380712040]
>>> map(id, a[1:]
... )
[4380712040]
Natürlich werden die Referenzen selbst kopiert. Jeder kostet 8 Bytes auf einem 64-Bit-Computer. Und jede Liste hat ihren eigenen Speicheraufwand von 72 Bytes:
>>> for i in range(len(a)):
... x = a[:i]
... print('len: {}'.format(len(x)))
... print('size: {}'.format(sys.getsizeof(x)))
...
len: 0
size: 72
len: 1
size: 80
len: 2
size: 88
Wie Joe Pinsonault uns erinnert , summiert sich dieser Overhead. Und ganzzahlige Objekte selbst sind nicht sehr groß - sie sind dreimal größer als Referenzen. Dies spart Ihnen im absoluten Sinne etwas Speicherplatz, aber asymptotisch kann es hilfreich sein, mehrere Listen, die "Ansichten" sind, in demselben Speicher zu haben.
Leider bietet Python keine einfache Möglichkeit, Objekte, die "Ansichten" sind, in Listen zu erstellen. Oder vielleicht sollte ich "zum Glück" sagen! Sie müssen sich also keine Gedanken darüber machen, woher eine Scheibe kommt. Änderungen am Original wirken sich nicht auf das Slice aus. Insgesamt erleichtert dies das Nachdenken über das Verhalten eines Programms erheblich.
Wenn Sie wirklich Speicher sparen möchten, indem Sie mit Ansichten arbeiten, sollten Sie numpy
Arrays verwenden. Wenn Sie ein numpy
Array in Scheiben schneiden , wird der Speicher zwischen dem Slice und dem Original geteilt:
>>> a = numpy.arange(3)
>>> a
array([0, 1, 2])
>>> b = a[1:3]
>>> b
array([1, 2])
Was passiert, wenn wir etwas ändern a
und erneut betrachten b
?
>>> a[2] = 1001
>>> b
array([ 1, 1001])
Dies bedeutet jedoch, dass Sie sicherstellen müssen, dass Sie beim Ändern eines Objekts nicht versehentlich ein anderes Objekt ändern. Das ist der Kompromiss, wenn Sie verwenden numpy
: weniger Arbeit für den Computer und mehr Arbeit für den Programmierer!
id(2)
oder sogar id(1+1)
. Ein besseres Beispiel wäre zu verwenden a = [[], [], []]
.
Je nachdem, was Sie tun, können Sie möglicherweise verwenden islice
.
Da es über Iteration arbeitet, werden keine neuen Listen erstellt, sondern es werden einfach Iteratoren erstellt, die yield
Elemente aus der ursprünglichen Liste enthalten, wie für ihre Bereiche angefordert.
Eine einfache Alternative dazu islice
durchläuft keine Listenelemente, die nicht benötigt werden:
def listslice(xs, *args):
for i in range(len(xs))[slice(*args)]:
yield xs[i]
Verwendung:
>>> xs = [0, 2, 4, 6, 8, 10]
>>> for x in listslice(xs, 2, 4):
... print(x)
4
6
Ich habe eine ListView
Klasse geschrieben, die es vermeidet, auch nur den Rücken der Liste zu kopieren:
https://gist.github.com/3noch/b5f3175cfe39aea71ca4d07469570047
Dies unterstützt verschachteltes Schneiden, sodass Sie weiter in die Ansicht schneiden können, um engere Ansichten zu erhalten. Zum Beispiel : ListView(list(range(10)))[4:][2:][1] == 7
.
Beachten Sie, dass dies nicht vollständig gebacken ist und ein gutes Stück mehr Fehlerprüfung verdient, wenn die zugrunde liegende Liste zusammen mit einer Testsuite mutiert ist.
Im Allgemeinen ist das Aufteilen von Listen die beste Option.
Hier ist ein schneller Leistungsvergleich:
from timeit import timeit
from itertools import islice
for size in (10**4, 10**5, 10**6):
L = list(range(size))
S = size // 2
def sum_slice(): return sum(L[S:])
def sum_islice(): return sum(islice(L, S, None))
def sum_for(): return sum(L[i] for i in range(S, len(L)))
assert sum_slice() == sum_islice()
assert sum_slice() == sum_for()
for method in (sum_slice, sum_islice, sum_for):
print(f'Size={size}, method={method.__name__}, time={timeit(method, number=1000)} ms')
Ergebnisse:
Size=10000, method=sum_slice, time=0.0298 ms
Size=10000, method=sum_islice, time=0.0449 ms
Size=10000, method=sum_for, time=0.2500 ms
Size=100000, method=sum_slice, time=0.3262 ms
Size=100000, method=sum_islice, time=0.4492 ms
Size=100000, method=sum_for, time=2.4849 ms
Size=1000000, method=sum_slice, time=5.4092 ms
Size=1000000, method=sum_islice, time=5.1139 ms
Size=1000000, method=sum_for, time=26.198 ms