Li

Listen in Python

96 Übungen

Über Listen

Eine list ist eine veränderbare Sammlung von Elementen in Reihenfolge. Wie die meisten Sammlungen (siehe die eingebauten tuple, dict und set) kann eine Liste Referenzen auf beliebige (oder mehrere) Datentypen enthalten, auch auf andere Listen. Wie jede Sequenz lassen sich Elemente über die Nummer 0-based index von links und -1-based index von rechts ansprechen. Listen lassen sich ganz oder teilweise über die Slice-Notation oder <list>.copy() kopieren.

Listen unterstützen sowohl gemeinsame als auch verändernde Sequenzoperationen wie min()/max(), <list>.index(), .append() und .reverse(). Über die Elemente einer Liste kannst du mit dem Konstrukt for item in <list> iterieren. for index, item in enumerate(<list>) kannst du verwenden, wenn du sowohl den Index als auch den Wert eines Elements brauchst.

Listen sind als dynamische Arrays implementiert, ähnlich dem Arraylist-Typ in Java, und werden am häufigsten verwendet, um Gruppen ähnlicher Daten (Strings, Zahlen, Mengen usw.) unbekannter Länge zu speichern (die Anzahl der Einträge kann beliebig wachsen oder schrumpfen).

Der Zugriff auf Elemente, das Prüfen der Zugehörigkeit mit in oder das Anhängen von Elementen an die „rechte“ Seite einer Liste sind alle sehr effizient. Das Voranstellen (Anhängen an die „linke“ Seite) oder das Einfügen in die Mitte einer Liste ist deutlich weniger effizient, weil diese Operationen Elemente verschieben müssen, um sie in der Reihenfolge zu halten. Eine ähnliche Datenstruktur, die speichereffiziente appends/pops von beiden Seiten unterstützt, ist collections.deque, die in beide Richtungen ungefähr die gleiche O(1)-Leistung bietet.

Weil Listen veränderbar sind und Referenzen auf beliebige Python-Objekte enthalten können, belegen sie außerdem mehr Speicher als ein array.array oder ein tuple (der unveränderlich ist) mit der gleichen scheinbaren Länge. Trotzdem sind Listen eine extrem flexible und nützliche Datenstruktur, und viele eingebaute Methoden und Operationen in Python liefern Listen als Ergebnis.

Erstellung

Eine list lässt sich als Literal mit eckigen [] Klammern und Kommas zwischen den Elementen deklarieren:

>>> no_elements = []

>>> no_elements
[]

>>> one_element = ["Guava"]

>>> one_element
['Guava']

>>> elements_separated_with_commas = ["Parrot", "Bird", 334782]

>>> elements_separated_with_commas
['Parrot', 'Bird', 334782]

Zur besseren Lesbarkeit kannst du Zeilenumbrüche verwenden, wenn eine Liste viele Elemente oder verschachtelte Datenstrukturen enthält:

>>> lots_of_entries = [
...    "Rose",
...    "Sunflower",
...    "Poppy",
...    "Pansy",
...    "Tulip",
...    "Fuchsia",
...    "Cyclamen",
...    "Lavender"
... ]

>>> lots_of_entries
['Rose', 'Sunflower', 'Poppy', 'Pansy', 'Tulip', 'Fuchsia', 'Cyclamen', 'Lavender']


# Each data structure is on its own line to help clarify what they are.
>>> nested_data_structures = [
...    {"fish": "gold", "monkey": "brown", "parrot": "grey"},
...    ("fish", "mammal", "bird"),
...    ['water', 'jungle', 'sky']
... ]

>>> nested_data_structures
[{'fish': 'gold', 'monkey': 'brown', 'parrot': 'grey'}, ('fish', 'mammal', 'bird'), ['water', 'jungle', 'sky']]

Der Konstruktor list() kann leer oder mit einem Iterable als Argument verwendet werden. Der Konstruktor durchläuft die Elemente des Iterables nacheinander und fügt sie der Liste in der Reihenfolge hinzu:

>>> no_elements = list()
>>> no_elements
[]

# The tuple is unpacked and each element is added.
>>> multiple_elements_from_tuple = list(("Parrot", "Bird", 334782))

>>> multiple_elements_from_tuple
['Parrot', 'Bird', 334782]

# The set is unpacked and each element is added.
>>> multiple_elements_from_set = list({2, 3, 5, 7, 11})

>>> multiple_elements_from_set
[2, 3, 5, 7, 11]

Die Ergebnisse können überraschen, wenn du den Listenkonstruktor mit einem String oder einem Dictionary verwendest:

# String elements (Unicode code points) are iterated through and added *individually*.
>>> multiple_elements_string = list("Timbuktu")

>>> multiple_elements_string
['T', 'i', 'm', 'b', 'u', 'k', 't', 'u']

# Unicode separators and positioning code points are also added *individually*.
>>> multiple_code_points_string = list('अभ्यास')

>>> multiple_code_points_string
['अ', 'भ', '्', 'य', 'ा', 'स']

# The iteration default for dictionaries is over the keys, so only key data is inserted into the list.
>>> source_data = {"fish": "gold", "monkey": "brown"}
>>> list(source_data)
['fish', 'monkey']

Weil der Konstruktor list() nur Iterables (oder nichts) als Argumente akzeptiert, lösen Objekte, die nicht iterierbar sind, einen TypeError aus. Deshalb ist es viel einfacher, eine Liste mit einem einzigen Element über die Literalschreibweise zu erstellen.

# Numbers are not iterable, and so attempting to create a list with a number passed to the constructor fails.
>>> one_element = list(16)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable

# Tuples *are* iterable, so passing a one-element tuple to the constructor does work, but it's awkward
>>> one_element_from_iterable = list((16,))

>>> one_element_from_iterable
[16]

Zugriff auf Elemente

Elemente in Listen (ebenso wie Elemente in anderen Sequenztypen wie str und tuple) erreichst du über die Klammernotation. Indizes können von left --> right (beginnend bei null) oder von right --> left (beginnend bei -1) laufen.

Index von links ⟹






0
👇🏾
1
👇🏾
2
👇🏾
3
👇🏾
4
👇🏾
5
👇🏾
P y t h o n
👆🏾
-6
👆🏾
-5
👆🏾
-4
👆🏾
-3
👆🏾
-2
👆🏾
-1





⟸ Index von rechts
>>> breakfast_foods = ["Oatmeal", "Fruit Salad", "Eggs", "Toast"]

# Oatmeal is at index 0 or index -4.
>>> breakfast_foods[0]
'Oatmeal'

>>> breakfast_foods[-4]
'Oatmeal'

# Eggs are at index -2 or 2
>>> breakfast_foods[-2]
'Eggs'

>>> breakfast_foods[2]
'Eggs'

# Toast is at -1
>>> breakfast_foods[-1]
'Toast'

Auf einen Abschnitt einer Liste greifst du über die Slice-Notation (<list>[<start>:<stop>]) zu. Ein Slice ist als Elementfolge an Position index definiert, für die start <= index < stop gilt. Slicing gibt eine Kopie der „herausgeschnittenen“ Elemente zurück und verändert die ursprüngliche list nicht.

Ein step-Parameter kann im Slice ebenfalls verwendet werden (<list>[<start>:<stop>:<step>]), um die zurückgegebenen Elemente zu „überspringen“ oder zu filtern (zum Beispiel wählt ein step von 2 jedes zweite Element im Abschnitt aus):

>>> colors = ["Red", "Purple", "Green", "Yellow", "Orange", "Pink", "Blue", "Grey"]

# If there is no step parameter, the step is assumed to be 1.
>>> middle_colors = colors[2:6]

>>> middle_colors
['Green', 'Yellow', 'Orange', 'Pink']

# If the start or stop parameters are omitted, the slice will
# start at index zero, and will stop at the end of the list.
>>> primary_colors = colors[::3]

>>> primary_colors
['Red', 'Yellow', 'Blue']

Mit Listen arbeiten

Listen stellen einen Iterator bereit und lassen sich genauso wie andere Sequenztypen durchlaufen, entweder mit for item in <list> oder mit for index, item in enumerate(<list>):

# Make a list, and then loop through it to print out the elements
>>> colors = ["Orange", "Green", "Grey", "Blue"]
>>> for item in colors:
...     print(item)

Orange
Green
Grey
Blue


# Print the same list, but with the indexes of the colors included
>>> colors = ["Orange", "Green", "Grey", "Blue"]
>>> for index, item in enumerate(colors):
...     print(item, ":", index)

Orange : 0
Green : 1
Grey : 2
Blue : 3


# Start with a list of numbers and then loop through and print out their cubes.
>>> numbers_to_cube = [5, 13, 12, 16]
>>> for number in numbers_to_cube:
...     print(number**3)

125
2197
1728
4096

Eine häufige Methode, eine Liste von Werten aufzubauen, ist die Verwendung von <list>.append() in einer Schleife:

>>> cubes_to_1000 = []
>>> for number in range(11):
...    cubes_to_1000.append(number**3)

>>> cubes_to_1000
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

Listen lassen sich auch mit verschiedenen Techniken kombinieren:

# Using the plus + operator unpacks each list and creates a new list, but it is not efficient.
>>> new_via_concatenate = ["George", 5] + ["cat", "Tabby"]

>>> new_via_concatenate
['George', 5, 'cat', 'Tabby']

# Likewise, using the multiplication operator * is the equivalent of using + n times.
>>> first_group = ["cat", "dog", "elephant"]
>>> multiplied_group = first_group * 3

>>> multiplied_group
['cat', 'dog', 'elephant', 'cat', 'dog', 'elephant', 'cat', 'dog', 'elephant']

# Another method for combining 2 lists is to use slice assignment or a loop-append.
# This assigns the second list to index 0 in the first list.
>>> first_one = ["cat", "Tabby"]
>>> second_one = ["George", 5]
>>> first_one[0:0] = second_one

>>> first_one
['George', 5, 'cat', 'Tabby']

# This loops through the first list and appends its items to the end of the second list.
>>> first_one = ["cat", "Tabby"]
>>> second_one = ["George", 5]

>>> for item in first_one:
...      second_one.append(item)

>>> second_one
['George', 5, 'cat', 'Tabby']

Ein paar Vorsichtsmaßnahmen

Zur Erinnerung: Variablen in Python sind Labels, die auf darunterliegende Objekte zeigen. lists fügen eine weitere Ebene als Containerobjekte hinzu: Sie halten Objekt-Referenzen für ihre gesammelten Elemente. Wenn du nicht sorgfältig damit umgehst, kann das bei der Arbeit mit Listen zu verschiedenen Problemen führen.

Einem Objekt mehr als einen Variablennamen zuweisen

Wenn du ein list-Objekt einem neuen Variablen_namen_ zuweist, wird weder das list-Objekt noch seine Elemente kopiert. Jede Änderung an den Elementen der list unter dem neuen Namen wirkt sich auf das Original aus.

Eine shallow_copy über list.copy() oder ein Slice vermeidet diese Verkomplizierung durch Referenzen auf der ersten Ebene. Eine shallow_copy erzeugt ein neues list-Objekt, legt aber keine neuen Objekte für die enthaltenen Listen_elemente_ an. Diese Art der Kopie reicht normalerweise aus, um Elemente unabhängig aus den beiden list-Objekten hinzuzufügen oder zu entfernen und so effektiv zwei „getrennte“ Listen zu haben.

>>> actual_names = ["Tony", "Natasha", "Thor", "Bruce"]

# Assigning a new variable name does not make a copy of the container or its data.
>>> same_list = actual_names

#  Altering the list via the new name is the same as altering the list via the old name.
>>> same_list.append("Clarke")
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]

>>> actual_names
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]

#  Likewise, altering the data in the list via the original name will also alter the data under the new name.
>>> actual_names[0] = "Wanda"
['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke']

# If you copy the list, there will be two separate list objects which can be changed independently.
>>> copied_list = actual_names.copy()
>>> copied_list[0] = "Tony"

>>> actual_names
['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke']

>>> copied_list
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]

Diese Referenzproblematik verschärft sich, wenn du mit verschachtelten oder vervielfachten Listen arbeitest (die folgenden Beispiele stammen aus dem hervorragenden Blogbeitrag Names and values: making a game board von Ned Batchelder aus dem Jahr 2013):

from pprint import pprint

# This will produce a game grid that is 8x8, pre-populated with zeros.
>>> game_grid = [[0]*8]*8

>>> pprint(game_grid)
[[0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0]]

# An attempt to put a "X" in the bottom right corner.
>>> game_grid[7][7] = "X"

# This attempt doesn't work because all the rows are referencing the same underlying list object.
>>> pprint(game_grid)
[[0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X']]

Aber in diesem Fall reicht eine shallow_copy aus, um das gewünschte Verhalten zu erreichen:

from pprint import pprint

# This loop will safely produce a game grid that is 8x8, pre-populated with zeros
>>> game_grid = []
>>> filled_row = [0] * 8
>>> for row in range(8):
...    game_grid.append(filled_row.copy()) # This is making a new shallow copy of the inner list object each iteration.

>>> pprint(game_grid)
[[0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0]]

# An attempt to put a "X" in the bottom right corner.
>>> game_grid[7][7] = "X"

# The game grid now works the way we expect it to!
>>> pprint(game_grid)
[[0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 'X']]

Wie bereits erwähnt, sind Listen Container von Referenzen, daher gibt es eine zweite Ebene möglicher Komplikationen. Wenn eine Liste Variablen, Objekte oder verschachtelte Datenstrukturen enthält, werden diese Referenzen der zweiten Ebene über shallow_copy oder ein Slice nicht kopiert. Das Verändern der zugrunde liegenden Objekte wirkt sich dann auf jede einzelne Kopie aus, da jedes list-Objekt nur Referenzen enthält, die auf die enthaltenen Elemente zeigen.

from pprint import pprint

>>> pprint(game_grid)
[[0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 'X']]

# We'd like a new board, so we make a shallow copy.
>>> new_game_grid = game_grid.copy()

# But a shallow copy doesn't copy the contained references or objects.
>>> new_game_grid[0][0] = 'X'

# So changing the items in the copy also changes the originals items.
>>>  pprint(game_grid)
[['X', 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 'X']]

Verwandte Datentypen

Listen werden oft als Stapel und Warteschlangen verwendet, obwohl ihre zugrunde liegende Implementierung das Voranstellen und Einfügen langsam macht. Das Modul collections bietet eine deque-Variante, die für schnelles Anhängen und Entfernen an beiden Enden optimiert und als doppelt verkettete Liste implementiert ist. Verschachtelte Listen werden auch verwendet, um kleine Matrizen zu modellieren, obwohl die Bibliotheken Numpy und Pandas für effiziente Matrix- und Tabellendatenmanipulation deutlich robuster sind. Das Modul collections bietet außerdem einen UserList-Typ, der an spezielle Listenanforderungen angepasst werden kann.

Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab

Lerne Listen

Das Üben ist gesperrt

Schalte 5 weitere Übungen frei, um Listen zu üben