Python: Wie funktioniert Slicing?
Slicing gehört zu den Dingen, die Python angenehm machen — und zu denen, die man halb lernt und danach errät. Hier die Grammatik, die beiden Verhaltensweisen die überraschen, und was eine Slice-Kopie tatsächlich kopiert.
Die Grammatik
a[start:stop:step], wobei jeder Teil weggelassen werden darf:
a : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
a[2:5] : [2, 3, 4] <- stop is exclusive
a[:3] : [0, 1, 2]
a[7:] : [7, 8, 9]
a[::2] : [0, 2, 4, 6, 8]
a[1:8:3] : [1, 4, 7]
a[:] : [0, 1, ...] <- a copy
Dass stop ausgeschlossen ist, sorgt dafür, dass a[:n] und a[n:] ohne
Überlappung und ohne Lücke zusammenpassen. Und es ist der Grund, warum
len(a[x:y]) schlicht y - x ist, solange beide im gültigen Bereich liegen.
Negative Indizes zählen vom Ende:
a[-1] : 9
a[-3:] : [7, 8, 9]
a[:-3] : [0, 1, 2, 3, 4, 5, 6]
Umdrehen und die Richtungsregel
a[::-1] : [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
a[::-2] : [9, 7, 5, 3, 1]
Bei negativer Schrittweite läuft es rückwärts, start muss also hinter
stop liegen:
a[5:2:-1] : [5, 4, 3] <- start > stop when step is negative
a[2:5:-1] : [] <- empty, the bounds point the wrong way
Die zweite Zeile ist der Fehler, auf den man achten muss. Es wirft nichts, sondern liefert eine leere Liste — und dein Code läuft mit nichts weiter.
Slices werfen nie einen IndexError
a[20:30] : []
a[5:100] : [5, 6, 7, 8, 9]
a[20] -> IndexError: list index out of range
Indizieren ist streng, Slicing begrenzt. Das ist wirklich nützlich: Deshalb
braucht seite[offset:offset + groesse] keine Bereichsprüfung, und deshalb
kommt die Schleife im Artikel über das Aufteilen von Listen ohne Sonderfall für
den letzten kurzen Block aus.
Es heißt allerdings auch, dass ein Tippfehler in einem Slice eine leere Liste liefert statt eines Fehlers.
Eine Slice-Kopie ist flach
a[:] ist die verbreitete Schreibweise, um eine Liste zu kopieren. Sie kopiert
eine Ebene:
copy is nested : False
copy[0] is nested[0] : True <- inner lists shared
after copy[0].append(99), nested = [[1, 2, 99], [3, 4]]
Die äußere Liste ist neu. Die inneren sind dieselben Objekte. Ein append über
die vermeintliche Kopie hat das Original verändert.
list.copy() verhält sich genauso. Nur
copy.deepcopy
liefert dir eine unabhängige Struktur:
orig[:] inner shared : True
orig.copy() inner shared : True
copy.deepcopy inner shared : False
Einem Slice etwas zuweisen
Man kann einem Slice auch etwas zuweisen, und bei einem einfachen Slice müssen die Längen nicht übereinstimmen — die Liste passt ihre Größe an:
b[1:3] = ['x','y','z'] -> [0, 'x', 'y', 'z', 3, 4]
b[1:4] = [] -> [0, 3, 4]
b[1:4] = [] ist damit eine andere Schreibweise für del b[1:4].
Bei einem erweiterten Slice — einem mit Schrittweite — ist das anders. Dort müssen die Längen exakt passen:
c[::2] = [0, 0] -> ValueError: attempt to assign sequence of size 2 to extended slice of size 3
c[::2] = [9,9,9] -> [9, 1, 9, 3, 9]
Das ergibt Sinn: Es gibt keine vernünftige Art, in jede zweite Position etwas einzufügen.
Das Slice-Objekt
Die Syntax ist Zucker für ein
slice-Objekt, und
du kannst dir selbst eines bauen:
slice(2, 5) : slice(2, 5, None)
a[s] : [2, 3, 4]
s.indices(len(a)) : (2, 5, 1)
Das ist deshalb interessant, weil man ihm einen Namen geben kann:
LETZTE_DREI = slice(-3, None)
a[LETZTE_DREI] -> [7, 8, 9]
Beim Zerlegen von Datensätzen mit fester Feldbreite sind eine Handvoll benannter Slices deutlich besser als überall verstreute Zahlen.
Deine eigenen Klassen bekommen dieses Objekt in __getitem__:
p[3] : got 3 of type int
p[1:2] : got slice(1, 2, None) of type slice
p[1:2:3] : got slice(1, 2, 3) of type slice
Es funktioniert mit jeder Sequenz
'hello world'[::2] : 'hlowrd'
(1,2,3,4)[1:3] : (2, 3)
b'abcd'[1:3] : b'bc'
range(10)[2:5] : range(2, 5) <- still a range, lazily
Der range-Fall ist hübsch: Ein Slice über ein range liefert wieder ein
range, ohne irgendetwas zu materialisieren.
Ein Dictionary ist keine Sequenz, dort gibt es also nichts zu slicen — und der Fehler ist nicht der, den man erwarten würde:
a dict -> KeyError: slice('a', 'b', None)
Kein TypeError. Ohne Ordnung gibt es nichts zu interpretieren, also wird das
Slice-Objekt einfach als Schlüssel verwendet — und den gibt es nicht.
Hinweis zu Netcup (Werbung)
Der deutsche Hoster Netcup bietet unter anderem günstige und zugleich leistungsstarke Webhosting Pakete, KVM-basierte Root Server und dezidierte Server an. Mit unseren Gutscheincodes kannst du noch mehr Geld sparen (6€ bei deiner ersten Bestellung, 30% Rabatt auf alle KVM-basierten Root Server, ...).