Vorlesungsskript 12
Listenoperationen
In diesem Vorlesungsskript lernen wir den Unterschied zwischen unveränderlichen und veränderlichen Objekten kennen und den Unterschied zwischen destruktiven und nichtdestruktiven Operationen. Gewappnet mit diesem Wissen wenden wir uns dann Pythons Listenoperationen zu – den Anweisungen, mit denen wir Listen verarbeiten können. Schließlich lernen wir noch ein paar Operationen kennen, die speziell für Listen von Zahlen bzw. für Listen von Strings nützlich sind.
Unveränderliche Objekte
Die Datentypen, mit denen wir uns bisher beschäftigt haben – Zahlen und Strings – sind unveränderlich (engl. immutable). Ein Objekt dieser Datentypen, einmal erzeugt und als Folge von Nullen und Einsen im Arbeitsspeicher abgelegt, kann nicht mehr verändert werden. Es bleibt gleich, bis es nicht mehr gebraucht und automatisch aus dem Arbeitsspeicher gelöscht wird.
Alle Ausdrücke, die auf Zahlen und Strings operieren, geben also tatsächlich neue Objekte zurück; die Input-Objekte bleiben unangetastet. Zum Beispiel:
>>> a = 'dog'
>>> b = 'house'
>>> c = a + b
>>> a
'dog'
>>> b
'house'
>>> c
'doghouse'
In der dritten Anweisung wird aus 'dog'
und 'house'
ein neuer String erzeugt, die beiden
Objekte werden aber nicht verändert und bleiben in a
bzw. b
gespeichert.
Zahlen- und Stringobjekte sind also unveränderlich. Was natürlich schon verändert werden kann, ist der Wert einer Variablen. Man ersetzt dann das in der Variablen gespeicherte Objekt durch ein anderes Objekt. Zum Beispiel:
>>> a = 'dog'
>>> a = a + 'house'
>>> a
'doghouse'
Das alte Objekt (in diesem Beispiel der String 'dog'
) bleibt zunächst im
Arbeitsspeicher, obwohl es nicht mehr in der Variablen a
gespeichert ist. Es wird ggf.
später automatisch gelöscht, wenn es nicht mehr gebraucht
wird.
Listen sind veränderlich
Im Gegensatz zu Zahlen und Strings sind Listen veränderlich (engl. mutable). Man kann zu einer Liste Elemente hinzufügen und welche wegnehmen und man hat hinterher immer noch dasselbe Listenobjekt, aber sein Inhalt hat sich verändert.
Bei Anweisungen, die auf veränderlichen Objekten wie Listen operieren, unterscheidet man daher zwischen destruktiven und nichtdestruktiven Anweisungen. Destruktive Anweisungen verändern den Inhalt des bestehenden Objekts und der bisherige Zustand geht verloren. Nichtdestruktive Anweisungen verhalten sich wie bei Zahlen und Strings: Es wird ein neues Objekt erzeugt und das alte bleibt unverändert bestehen.
Vergleichsoperatoren für Listen
Ganz ähnlich wie bei Strings lassen sich auch bei Listen die
Vergleichsoperatoren ==
, !=
, <
,
>
, <=
und >=
nutzen. Zwei
Listen sind „gleich“, wenn sie die gleichen Elemente in
derselben Reihenfolge haben. Die
Standard-Sortierreihenfolge von Listen, nach der
„kleiner als“ und „größer als“ bestimmt werden, ergibt
sich so, dass nach dem ersten Element sortiert wird.
Bei gleichem ersten Element wird nach dem zweiten
Element sortiert, usw. Zum Beispiel:
>>> [23, 42] == [42, 23]
False
>>> [23, 42] == [23, 42]
True
>>> [1.0, 'a'] == [1, 'a']
True
>>> [] == [None]
False
>>> [23, 42] > [23, 42]
False
>>> [23, 43] > [23, 42]
True
>>> [22, 43] > [23, 42]
False
Nichtdestruktive Listenoperationen
Folgende Ausdrücke (Operatoren-, Indexing- und Slicing-Ausdrücke, Funktions- und Methodenaufrufe) lassen sich mit Listen verwenden, ohne die zugrundeliegenden Listen zu verändern:
Ausdruck | Gibt zurück |
---|---|
x in l |
True , wenn das Element x in der Liste l vorkommt, sonst False |
x not in l |
False , wenn das Element x in der Liste l vorkommt, sonst True |
l + m |
eine neue Liste mit den Elementen der Listen l und m (die Konkatenation der beiden Listen) |
n * l oder l * n |
eine neue Liste mit den Elementen der Liste l , n -mal wiederholt |
l[i] |
das Element am Index i in der Liste l (wie bei Strings gezählt beginnend bei 0 bzw. bei negativen Indizes von hinten bei -1) |
l[i:j] |
eine Subliste von l , beginnend mit dem Element am Index i und endend vor dem Element am Index j (wie auch beim String-Slicing können Indizes weggelassen und ggf. eine Schrittlänge angegeben werden) |
len(l) |
Länge der Liste l (Anzahl ihrer Elemente) |
l.index(x) |
wenn x ein Element der Liste l ist: das wievielte Element es ist (zählend von 0, beginnend am Anfang)ansonsten tritt ein Fehler auf |
l.count(x) |
wie oft das Element x in der Liste l vorkommt |
l.copy() |
eine neue Liste mit denselben Elementen wie l |
sorted(l) |
eine neue Liste mit denselben Elementen wie l , aber entsprechend ihrer Standard-Sortierreihenfolge sortiert |
Zum Beispiel:
>>> 'e' in ['a', 'b', 'c', 'd']
False
>>> 'b' in ['a', 'b', 'c', 'd']
True
>>> ['b', 'c'] in ['a', 'b', 'c', 'd']
False
>>> ['b', 'c'] in ['a', ['b', 'c'], 'd']
True
>>> 'e' not in ['a', 'b', 'c', 'd']
True
>>> ['a'] + ['b', 'c', 'd']
['a', 'b', 'c', 'd']
>>> ['a', 'b'] * 3
['a', 'b', 'a', 'b', 'a', 'b']
>>> 3 * ['a', 'b']
['a', 'b', 'a', 'b', 'a', 'b']
>>> len(['a', 'b', 'c', 'd'])
4
>>> len(['a', ['b', 'c'], 'd'])
3
>>> ['a', 'b', 'c', 'd'][1]
'b'
>>> ['a', 'b', 'c', 'd'][2]
'c'
>>> ['a', ['b', 'c'], 'd'][1]
['b', 'c']
>>> ['a', ['b', 'c'], 'd'][1][0]
'b'
>>> ['a', ['b', 'c'], 'd'][2]
'd'
>>> ['a', 'b', 'c', 'd'][1:3]
['b', 'c']
>>> ['a', ['b', 'c'], 'd'][1:3]
[['b', 'c'], 'd']
>>> ['a', ['b', 'c'], 'd'][1:]
[['b', 'c'], 'd']
>>> ['a', ['b', 'c'], 'd'][:2]
['a', ['b', 'c']]
>>> ['a', 'b', 'c', 'd'].index('c')
2
>>> ['a', ['b', 'c'], 'd'].index('c')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: 'c' is not in list
>>> ['a', 'b', 'c', 'd', 'a'].count('b')
1
>>> ['a', 'b', 'c', 'd', 'a'].count('a')
2
>>> l = ['a', 'b', 'c', 'd']
>>> m = l.copy()
>>> l.append('e')
>>> l
['a', 'b', 'c', 'd', 'e']
>>> m
['a', 'b', 'c', 'd']
>>> sorted(['b', 'a', 'd', 'c'])
['a', 'b', 'c', 'd']
Destruktive Listenoperationen
Die folgenden Anweisungen verändern bestehende Listen. Manche davon sind
Zuweisungen, andere sind Löschanweisungen und wieder andere sind Ausdrücke.
Auch die Ausdrücke geben aber nichts zurück, genauer gesagt: Sie geben
None
zurück (mit Ausnahme der Methode
pop
). Der Nutzen dieser Ausdrücke liegt nicht in
ihren Rückgabewerten, sondern in dem Effekt, den sie auf die bestehenden
Objekte haben.
Anweisung | Wirkung |
---|---|
l[i] = x |
Das Element der Liste mit dem Index i wird durch das Objekt x ersetzt. |
l[i:j] = m |
Die Elemente mit den Indizes i bis j - 1 werden entfernt und durch die Elemente von m ersetzt.* |
del l[i] |
Das Element der Liste mit dem Index i wird entfernt. |
del l[i:j] |
Die Elemente mit den Indizes i bis j - 1 werden entfernt.* |
l.append(x) |
Das Objekt x wird der Liste l am Ende hinzugefügt. |
l.insert(i, x) |
Das Objekt x wird der Liste l am Index i hinzugefügt. |
l.extend(m) oder l += m |
Die Elemente von m werden der Liste l am Ende hinzugefügt. |
l *= n |
Die Liste l wird so erweitert, dass sie dieselben Elemente wie vorher enthält, aber n -mal wiederholt. |
l.clear() |
Entfernt alle Elemente aus der Liste l . |
l.pop() |
Entfernt das letzte Element aus der Liste l und gibt es zurück. |
l.pop(i) |
Entfernt das Element am Index i aus der Liste l und gibt es zurück. |
l.remove(x) |
Sucht das erste Vorkommen des Objektes x in der Liste l und entfernt es. Wird das Objekt nicht gefunden, tritt ein Fehler auf. |
l.reverse() |
Kehrt die Reihenfolge der Elemente der Liste l um. |
l.sort() |
Sortiert die Liste l entsprechend der Standard-Sortierreihenfolge der Elemente. |
* Hier lassen sich i
und
j
auch weglassen und eine Schrittlänge hinzufügen,
wie bei Slicing-Ausdrücken.
Zum Beispiel:
>>> l = ['a', 'b', 'c', 'd']
>>> l[1] = 'B'
>>> l
['a', 'B', 'c', 'd']
>>> l[2:4] = ['C']
>>> l
['a', 'B', 'C']
>>> l[2:4] = ['C', 'D']
>>> l
['a', 'B', 'C', 'D']
>>> l[3:3] = [55]
>>> l
['a', 'B', 'C', 55, 'D']
>>> l[5:] = [0]
>>> l
['a', 'B', 'C', 55, 'D', 0]
>>> l[:1] = ['I']
>>> l
['I', 'B', 'C', 55, 'D', 0]
>>> del l[:3]
>>> l
[55, 'D', 0]
>>> del l[1]
>>> l
[55, 0]
>>> l.append(213)
>>> l
[55, 0, 213]
>>> l.insert(2, 44)
>>> l
[55, 0, 44, 213]
>>> l.extend([44, 1])
>>> l
[55, 0, 44, 213, 44, 1]
>>> l += [0]
>>> l
[55, 0, 44, 213, 44, 1, 0]
>>> l.pop()
0
>>> l
[55, 0, 44, 213, 44, 1]
>>> l.pop(1)
0
>>> l
[55, 44, 213, 44, 1]
>>> l.remove(44)
>>> l
[55, 213, 44, 1]
>>> l.reverse()
>>> l
[1, 44, 213, 55]
>>> l.sort()
>>> l
[1, 44, 55, 213]
>>> l *= 2
>>> l
[1, 44, 55, 213, 1, 44, 55, 213]
>>> l.clear()
>>> l
[]
Unerwartete Folgen von destruktiven Operationen
Destruktive Operationen ändern den Inhalt von Objekten, was mitunter unerwartete Folgen haben kann, wenn man nicht genau aufpasst. Zum Beispiel kann sich das Ergebnis eines Vergleichs zweier Listen ändern, wenn sich er Inhalt einer der Listen ändert. Betrachten Sie das folgende Beispiel:
>>> l = [23, 42]
>>> m = [23, 42]
>>> l == m
True
>>> l.append(101)
>>> l == m
False
Hier vergleichen wir zwei Listen, fügen dann der einen ein Element am Anfang hinzu und vergleichen dann wieder, mit anderem Ergebnis.
Zum anderen kann es zu Verwirrung kommen, wenn dasselbe Objekt in zwei Variablen gespeichert ist und man den Inhalt der einen Variablen ändert. Damit ändert man automatisch auch den Inhalt der anderen Variablen, weil er ja dasselbe Objekt ist. Zum Beispiel:
>>> carnivores = ['dog', 'cat']
>>> my_pets = carnivores
>>> my_pets
['dog', 'cat']
>>> my_pets.append('hamster')
>>> my_pets
['dog', 'cat', 'hamster']
>>> carnivores
['dog', 'cat', 'hamster']
Hier haben wir der Liste carnivores
unbeabsichtigt
das Element 'hamster'
hinzugefügt, weil wir nicht
beachtet haben, dass es dasselbe Listenobjekt ist wie my_pets
. Die Lösung wäre, auf der zweiten Zeile der
Variablen my_pets
nicht dasselbe Objekt zuzuweisen
wie carnivores
, sondern eine Kopie davon:
>>> carnivores = ['dog', 'cat']
>>> my_pets = carnivores.copy()
>>> my_pets
['dog', 'cat']
>>> my_pets.append('hamster')
>>> my_pets
['dog', 'cat', 'hamster']
>>> carnivores
['dog', 'cat']
Nichtdestruktive Operationen mit Listen von Zahlen
Die Funktionen min
und max
haben wir bereits mit mehreren Zahlen als
Argumente aufgerufen. Sie geben dann das kleinste bzw. größte Argument zurück.
Sie lassen sich aber auch mit nur einem Argument aufrufen, das dann eine Liste
sein muss – und geben dann das kleinste bzw. größte Element zurück.
Außerdem gibt es noch die Funktion sum
, das die Summe
aller Elemente einer Liste von Zahlen berechnet.
Ausdruck | Gibt zurück |
---|---|
min(l) |
die kleinste Zahl in der Liste l |
max(l) |
die größte Zahl in der Liste l |
sum(l) |
die Summe aller Zahlen in der Liste l |
Zum Beispiel:
>>> min([1, -42, 0])
-42
>>> max([1, -42, 0])
1
>>> sum([1, -42, 0])
-41
Mit Hilfe von sum
lässt sich leicht z.B. der
Mittelwert aller Zahlen in einer Liste berechnen, als Quotient der Summe und
der Anzahl der Elemente:
>>> my_numbers = [1, 2, 3, 4]
>>> sum(my_numbers) / len(my_numbers)
2.5
Nichtdestruktive Operationen mit Listen von Strings
Strings haben die Methode split
, mit der man den
String in eine Liste seiner Teilstrings zerlegen kann. Umgekehrt kann man mit
der join
-Methode eine Liste von Strings zu einem
String zusammenfügen.
Ausdruck | Gibt zurück |
---|---|
s.split() |
den String s , aufgeteilt in eine Liste von Strings anhand von beliebigen Whitespace-Zeichen als Trennzeichen |
s.split(sep) |
den String s , aufgeteilt in eine Liste von Strings anhand des Trennzeichens sep |
sep.join(l) |
die Strings in der Liste l zusammengefügt zu einem einzigen String, jeweils mit dem String sep dazwischen |
Die split
-Methode ist sehr nützlich, um
mehrspaltigen Input zu verarbeiten. Am einfachsten ist es, wenn die Felder im
Input durch Whitespace getrennt sind, selbst nie leer sind und keinen
Whitespace enthalten. Dann kann man die Methode ohne Argument aufrufen und
aller Whitespace wird automatisch entfernt, auch am Ende der Zeilen. Zum
Beispiel:
>>> ' ab cde fghi jklm\n'.split()
['ab', 'cde', 'fghi', 'jklm']
>>> 'der\tdie\tART\t241408360.16429\n'.split()
['der', 'die', 'ART', '241408360.16429']
Hier ist ein Beispielprogramm pos.py
, das
mehrspaltigen Input dieser Art liest und von jeder Zeile nur das Feld
mit dem Index 2
ausgibt:
import sys
for line in sys.stdin:
fields = line.split()
print(fields[2])
Auf die DeReKo-Frequenzliste angewendet, gibt das Programm eine Liste der Wortarten aus:
$ cat DeReKo-2014-II-MainArchive-STT.100000.freq | python3 pos.py
$,
$.
ART
ART
KON
$(
APPR
ART
$(
$(
…
Oft können die Felder im Input allerdings leer sein oder selbst Leerzeichen enthalten. Dann gibt der Aufruf der Methode wie oben nicht das gewünschte Ergebnis:
>>> 'New York\tNew York\tNE\t968363\n'.split()
['New', 'York', 'New', 'York', 'NE', '968363']
In diesem Fall muss man split
das Trennzeichen
(z.B. Tabulator oder Semikolon) als Argument übergeben. Nur dieses wird dann
als Trennzeichen verwendet, anderer Whitespace bleibt unangetastet:
>>> 'New York\tNew York\tNE\t968363\n'.split('\t')
['New York', 'New York', 'NE', '968363\n']
Newline-Zeichen am Ende muss man dann ggf. separat mit rstrip
entfernen:
>>> 'New York\tNew York\tNE\t968363\n'.rstrip().split('\t')
['New York', 'New York', 'NE', '968363']
>>> 'Donald Duck;Blumenstr. 13;Entenhausen\n'.rstrip().split(';')
['Donald Duck', 'Blumenstr. 13', 'Entenhausen']
Hier einige Beispiele für die join
-Methode:
>>> ';'.join(['Donald Duck', 'Blumenstr. 13', 'Entenhausen'])
'Donald Duck;Blumenstr. 13;Entenhausen'
>>> ' '.join(['Ente', 'Ente', 'Ente'])
'Ente Ente Ente'
>>> ''.join(['Ente', 'Ente', 'Ente'])
'EnteEnteEnte'