Li

Listes en Python

96 exercices

À propos de Listes

Une list est une collection modifiable d'éléments en séquence. Comme la plupart des collections (voir les types natifs tuple, dict et set), les listes peuvent contenir des références vers n'importe quel type de données, ou plusieurs à la fois, y compris d'autres listes. Comme pour toute séquence, on accède aux éléments par un numéro d'0-based index depuis la gauche et d'-1-based index depuis la droite. On peut copier les listes en tout ou en partie via la notation de tranche ou <list>.copy().

Les listes prennent en charge à la fois les opérations de séquence courantes et mutables, telles que min()/max(), <list>.index(), .append() et .reverse(). On peut parcourir les éléments d'une liste avec la construction for item in <list>. for index, item in enumerate(<list>) permet d'obtenir à la fois l'indice et la valeur de l'élément.

Les listes sont implémentées comme des tableaux dynamiques, semblables au type Arraylist de Java, et servent le plus souvent à stocker des groupes de données similaires (strings, nombres, ensembles, etc.) de longueur inconnue (le nombre d'entrées peut augmenter ou diminuer à volonté).

Accéder aux éléments, vérifier l'appartenance avec in ou ajouter des éléments du côté « droit » d'une liste sont autant d'opérations très efficaces. Ajouter en tête (du côté « gauche ») ou insérer au milieu d'une liste est bien moins efficace, car ces opérations obligent à décaler les éléments pour préserver leur ordre. Pour une structure de données similaire qui prend en charge des appends/pops économes en mémoire des deux côtés, voir collections.deque, dont les performances en O(1) sont à peu près les mêmes dans les deux sens.

Parce que les listes sont modifiables et peuvent contenir des références vers n'importe quel objet Python, elles occupent aussi plus de place en mémoire qu'un array.array ou un tuple (qui est immuable) de même longueur apparente. Malgré cela, les listes restent une structure de données extrêmement souple et utile, et de nombreuses méthodes et opérations natives de Python produisent des listes en sortie.

Construction

On peut déclarer une list sous forme de littéral avec des crochets [] et des virgules entre les éléments :

>>> no_elements = []

>>> no_elements
[]

>>> one_element = ["Guava"]

>>> one_element
['Guava']

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

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

Pour plus de lisibilité, on peut utiliser des retours à la ligne quand une liste contient beaucoup d'éléments ou des structures de données imbriquées :

>>> 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']]

Le constructeur list() peut être utilisé à vide ou avec un itérable en argument. Le constructeur parcourt alors les éléments de l'itérable et les ajoute à la liste dans l'ordre :

>>> 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]

Les résultats obtenus en passant une chaîne de caractères ou un dictionnaire au constructeur de liste peuvent surprendre :

# 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']

Comme le constructeur list() n'accepte comme arguments que des itérables (ou rien du tout), les objets qui ne sont pas itérables lèvent une TypeError. Par conséquent, il est bien plus simple de créer une liste à un seul élément avec la méthode littérale.

# 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]

Accède aux éléments

Les éléments d'une liste (ainsi que ceux des autres types de séquence comme str et tuple) sont accessibles via la notation par crochets. Les indices peuvent aller de left vers right (en partant de zéro) ou de right vers left (en partant de -1).

index depuis la gauche ⟹






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





⟸ index depuis la droite
>>> 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'

On accède à une partie d'une liste via la notation de tranche (<list>[<start>:<stop>]). Une tranche est définie comme une séquence d'éléments aux positions index telles que start <= index < stop. Le découpage renvoie une copie des éléments « tranchés » sans modifier la list d'origine.

On peut aussi utiliser un paramètre step dans la tranche (<list>[<start>:<stop>:<step>]) pour « sauter » ou filtrer les éléments renvoyés (par exemple, un step de 2 sélectionne un élément sur deux dans la section) :

>>> 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']

Manipule les listes

Les listes fournissent un itérateur et peuvent être parcourues de la même manière que les autres types de séquence, avec for item in <list> ou 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

Une façon courante de construire une liste de valeurs consiste à utiliser <list>.append() dans une boucle :

>>> 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]

On peut aussi combiner des listes de différentes manières :

# 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']

Quelques mises en garde

Rappelons qu'en Python, les variables sont des étiquettes qui pointent vers des objets sous-jacents. Les lists ajoutent une couche supplémentaire en tant qu'objets conteneurs : elles détiennent des références vers les objets qu'elles rassemblent. Si on ne les manipule pas correctement, cela peut entraîner plusieurs problèmes potentiels.

Affecte plus d'un nom de variable

Affecter un objet list à un nouveau nom de variable ne copie ni l'objet list ni ses éléments. Toute modification des éléments de la list sous le nouveau nom affecte l'originale.

Réaliser une shallow_copy avec list.copy() ou une tranche évite cette complication de référencement de premier niveau. Une shallow_copy crée un nouvel objet list, mais ne crée pas de nouveaux objets pour les éléments de la liste. Ce type de copie suffit généralement pour ajouter ou retirer des éléments des deux objets list de façon indépendante, et avoir ainsi deux listes « distinctes ».

>>> 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"]

Cette complication de référence s'aggrave quand on manipule des listes imbriquées ou multipliées (les exemples suivants sont tirés de l'excellent billet de blog de 2013 de Ned Batchelder, Names and values: making a game board) :

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']]

Mais dans ce cas, une shallow_copy suffit pour obtenir le comportement souhaité :

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']]

Comme mentionné plus haut, les listes sont des conteneurs de références, ce qui ajoute une seconde couche de complication potentielle. Si une liste contient des variables, des objets ou des structures de données imbriquées, ces références de second niveau ne sont pas copiées par une shallow_copy ou une tranche. Modifier les objets sous-jacents affecte alors toutes les copies, puisque chaque objet list ne contient que des références pointant vers les éléments qu'il contient.

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']]

Types de données apparentés

Les listes servent souvent de piles et de files, même si leur implémentation interne rend l'ajout en tête et l'insertion lents. Le module collections propose une variante deque optimisée pour des ajouts et des retraits rapides aux deux extrémités, implémentée comme une liste doublement chaînée. Les listes imbriquées servent aussi à modéliser de petites matrices, même si les bibliothèques Numpy et Pandas sont bien plus robustes pour manipuler efficacement des matrices et des données tabulaires. Le module collections fournit aussi un type UserList que l'on peut personnaliser pour répondre à des besoins particuliers.

Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet

Apprends Listes

L'entraînement est verrouillé

Déverrouille 5 exercices de plus pour t'entraîner sur Listes