Les conteneurs natifs de Python

Un conteneur Python est un objet qui contient une collection d’autres objets.

Dans Python, il existe 3 grandes catégories de conteneurs :

  1. les conteneurs séquentiels ou séquences dans lesquels les éléments sont accessibles grâce à leur position qui est donnée par un numéro (commençant à 0) et qu’on appelle indice.

    Parmi ces conteneurs, on trouve :

    • les chaînes de caractères (→ strings)

    • les listes (→ lists)

    • les n-uplets (→ tuples)

    • les intervalles (→ ranges)

    • les séquences binaires (→ binary sequences)

  2. les conteneurs à accès direct dans lesquels les éléments sont accessibles, non pas via leur position mais grâce à un identifiant qu’on appelle clé (→ key). Cette clé peut prendre différentes formes : (chaîne de )caractère(s), n-uplets mais aussi nombre entier (comme dans une séquence) ou flottant …​

    Le seul conteneur à accès direct disponible nativement dans Python est le dictionnaire (→ dictionnary) appelé aussi tableau associatif (→ mapping).

    La façon dont Python organise en mémoire les conteneurs à accès direct dépend d’un algorithme (→ algorithme de “hachage”) appliqué sur la clé de chaque élément. Ceci permet notamment un accès plus rapide aux éléments par rapport à ceux d’une séquence.

    Jusqu’à la version 3.6 de Python, on ne pouvait pas présumer de l’ordre des éléments d’un dictionnaire. Ainsi, leur ordre d’affichage n’était pas forcément le même que celui de leur création. De même lorsqu’on parcourait un dictionnaire, l’ordre n’était pas garanti.

    Depuis Python 3.7, ce comportement a changé, un dictionnaire est toujours affiché dans le même ordre que celui utilisé pour le remplir. De même, si on le parcourt, cet ordre est respecté.

  3. les ensembles dans lesquels on ne peut accéder aux éléments qu’à travers une boucle et non de manière individuelle (← conteneur itérable mais pas indexable)

    En natif, Python met à disposition 2 conteneurs de type ensemble :

    • les set

    • les frozenset

Les séquences

Opérations communes à toutes les séquences

  • accès aux éléments de la séquence à l’aide d’un indice indiqué entre crochets : seq[idx].

    Si l’indice est négatif, celui-ci indique une position en partant de la fin.

    seq[-1] désigne alors le dernier élément, seq[-2] l’avant-dernier élément etc…​

    Exemples sur une chaîne :
    >>> welcome = "Hello"
    >>> welcome[0]
    'H'
    >>> welcome[-1]
    'o'
  • l’extraction d’une partie de la séquence (→ slicing) : seq[idx_start:idx_stop:step]

    La partie extraite ne contient pas l’élément situé à l’indice idx_stop mais s’arrête juste avant.

    Exemples sur une chaîne :
    >>> welcome = "Hello"
    >>> welcome[0:2]
    'He'
    >>> welcome[:3]
    'Hel'
    >>> welcome[1:4]
    'ell'
    >>> welcome[0:4:2]
    'Hl'
    >>> welcome[-4:-1]
    'ell'
    >>> welcome[:-1]
    'Hell'
    >>> welcome[::2]
    'Hlo'
    >>> welcome[::-1]
    'olleH'
  • nombre d’éléments dans une séquence : len(seq)

    Exemples sur une chaîne :
    >>> welcome = "Hello"
    >>> len(welcome)
    5
  • test d’appartenance d’un élément : elt in seq et elt not in seq

    Exemples sur une chaîne :
    >>> welcome = "Hello"
    >>> 'l' in welcome
    True
    >>> 'z' in welcome
    False
    >>> 'z' not in welcome
    True
  • concaténation de séquence : +

    Exemple sur une chaîne :
    >>> welcome = "Hello"
    >>> welcome + ' world !'
    'Hello world !'
  • recherche de la 1ère occurence d’un élément : seq.index(elt)

    Exemples sur une chaîne :
    >>> welcome = "Hello world !"
    >>> welcome.index('w')
    6
    >>> welcome.index('l') # ne retourne que l'indice du 1er ‘l’ trouvé
    2
  • détermination du nombre d’éléments d’une certaine valeur : seq.count(elt)

    Exemple sur une chaîne :
    >>> welcome = "Hello world !"
    >>> welcome.count('l')
    3
  • détermination du minimum et du maximum d’une séquence : min(seq) et max(seq)

    • Veiller à écrire min(seq) ou max(seq) et non seq.min() ou seq.max()

    • La façon dont sont déterminés les minimum et maximum dépend du type de séquence.

      Par exemple, dans une chaîne de caractères, Python se base sur l’ordre lexicographique : ‘$` < ''`A’ < ‘Z’ < ‘z’ < ‘{’

    Exemples sur une chaîne :
    >>> welcome = "Hello world !"
    >>> min(welcome)
    ' '
    >>> max(welcome)
    'w'
  • la répétition de séquence : seq * n

    Exemples sur une chaîne :
    >>> laugh = "Ho ! "
    >>> laugh * 3
    'Ho ! Ho ! Ho ! '

Les listes

Une liste est une séquence modifiable — on dit aussi mutable — d’éléments éventuellement hétérogènes, c’est-à-dire de types différents.

Une liste est délimitée par des crochets (→ ‘[’ et ‘]’) et ses éléments sont séparés par des virgules (→ ‘,’).

Exemples :
# On définit une liste vide
>>> couleurs =[]
#  On lui ajoute une liste de chaînes de caractères
>>> couleurs += ['rouge', 'vert', 'bleu', 'magenta', 'cyan', 'jaune']
>>> couleurs
['rouge', 'vert', 'bleu', , 'magenta', 'cyan', 'jaune']
# on accède au 1er élément de la liste
>>> couleurs[ 0 ]
# On “tranche” la liste en ne prenant qu'1 élément sur 2
>>> couleurs[::2]
['rouge', 'bleu', 'cyan']
# On modifie le 3ème élément pour lui donner une valeur numérique
>>> couleurs[ 2 ] = 0x0000FF
>>> couleurs
['rouge', 'vert', 255, 'magenta', 'cyan', 'jaune']
>>>

Opérations de modification du contenu d’une liste

Comme les listes sont mutables, Python offre plusieurs moyens d’agir sur leur contenu :

Opération Résultat

s[i] = x

l’élément i de s est remplacé par x

s[i:j] = t

la tranche de s de i à j est remplacée par le contenu de l’itérable t

del s[i:j]

supprime tous les éléments de la liste (identique à s[i:j] = [])

s[i:j:k] = t

les éléments de s[i:j:k] sont remplacés par ceux de t

del s[i:j:k]

supprime les éléments de s[i:j:k] de la liste

s.append(x)

ajoute x à la fin de la séquence (identique à s[len(s):len(s)] = [x])

s.clear()

supprime tous les éléments de s (identique à del s[:])

s.copy()

crée une copie superficielle de s (identique à s[:])

s.extend(t) ou s += t

étend s avec le contenu de t (proche de s[len(s):len(s)] = t)

s *= n

met à jour s avec son contenu répété n fois

s.insert(i, x)

insère x dans s à l’indice donné par i (identique à s[i:i] = [x])

s.pop() ou s.pop(i)

récupère l’élément à la position i et le supprime de s

s.remove(x)

supprime le premier élément de s pour lequel s[i] est égal à x

s.reverse()

inverse sur place les éléments de s

Compréhension de liste (ou liste en compréhension)

La compréhension de liste est une manière de générer une liste à partir d’une itération avec une syntaxe très concise.

Exemples :
listeA = [x for x in range(5)]
print(listeA)
# affiche [0, 1, 2, 3, 4]
listeB = [c for c in "hello"]
print(listeB)
# affiche ['h', 'e', 'l', 'l', 'o']

Il est également possible d’appliquer un filtre lors d’une compréhension de liste afin de ne pas prendre un compte certains éléments lors de l’itération.

Exemples :
nbPairs = [i for i in range(11) if i % 2 == 0]
print(nbPairs)
# affiche [0, 2, 4, 6, 8, 10]
names = ['Charles', 'Susan', 'Patrick', 'George', 'Carol']
namesFiltered =  [n for n in names if n.count('e') == 0]
print(namesFiltered)
# affiche ['Susan', 'Patrick', 'Carol'] (prénoms ne contenant pas la lettre ‘e’)

Sans la compréhension de liste, le dernier exemple aurait pu être codé de la manière suivante :

names = ['Charles', 'Susan', 'Patrick', 'George', 'Carol']
namesFiltered = []
for n in names :
    if n.count('e') == 0 :
        namesFiltered.append(n)
print(namesFiltered)

…​ce qui est quand même, avouons-le, bien moins concis.

Les tuples

Un tuple (“n-uplet” en français) est une séquence quasiment identique à une liste. Elle ne diffère essentiellement de cette dernière que par le fait qu’elle n’est pas modifiable (on dit aussi immuable ou immutable en anglais).

Ceci permet à Python d’accéder à chaque élément beaucoup plus vite qu’à ceux d’une liste. De plus, ceux-ci prennent moins de place en mémoire.

Les éléments d’un tuple sont séparés par des virgules (→ ‘,’) et délimités par des parenthèses (→ ‘(’ et ‘)’) lorsque nécessaire.

Exemples :
# On définit un tuple vide
>>> t1 = () # parenthèses obligatoires
# On définit un tuple avec un seul élément
>>> t2 = 1, # Noter la ‘,’ pour forcer t2 à être un tuple et non un entier
# On définit un tuple contenant des chaînes de caractères
>>> t3 = "rouge", "vert", "bleu"
# On tente de modifier un des éléments de t3 ⇒ Erreur
>>> t3[0]="red"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
# On définit un tuple contenant 2 listes
>>> t4 = [1, 2, 3], ['A', 'B', 'C']

Les dictionnaires

Les dictionnaires (→ type dict en Python) sont des collections d’objets dans lesquels chaque valeur est associée à une clé.

Un dictionnaire est délimité par des accolades (→ ‘{’ et ‘}’) et ses éléments sont séparés par des virgules (→ ‘,’).

Exemple :
>>> # Définition d'un dictionnaire
>>> host = {"name": "pluton", "ip": (192, 168, 4, 100), "mask": 22}
>>> # Accès à l'élément du dictionnaire dont la clé est "ip"
>>> host["ip"]
(192, 168, 4, 100)

L’accès par clé plutôt que par indice fait ques les dictionnaires sont des conteneurs à accès direct et non séquentiel comme les list ou les tuples vus plus haut : ils sont donc nettement plus performants lorsqu’on doit les parcourir, les trier…​

Dans l’exemple précédent, on utilise une chaîne de caractères en guise de clé (→ “name”, “ip”, “mask”), mais on peut utiliser d’autres types de données à condition qu’ils puissent produire une valeur dîte de hashage : c’est le cas des entiers, flottant, booléens, tuples mais pas des listes et dictionnaires.

Exemple :
>>> # Définition dictionnaire avec clés hashables => OK
>>> d = {1: 'a', 3.14: 'b', True: 'c', (1,2): 'd'}
>>> # Tentative définition dictionnaire avec clé non hashable de type liste => NOK
>>> d = {[1,2]: 'a'}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> # Tentative définition dictionnaire avec clé non hashable de type dictionnaire => NOK
>>> d = {{'key':1}: 'a'}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'

Parcours d’un dictionnaire

Par défaut, on parcourt un dictionnaire à partir de ses clés. Cependant, il est également possible de le parcourir à partir de ses valeurs ou même à partir de ses clés et de ses valeurs (les 2 à la fois).

Exemple :
unites = {'kilo': 1e3, 'mega': 1e6, 'giga': 1e9, 'tera': 1e12}

# Par défaut, on parcourt les clés du dictionnaire
print("Parcours par clé : ")
for k in unites:
# identique à :
# for k in unites.keys():
    print(f"\t{k}")

# On parcourt les valeurs du dictionnaire
print("Parcours par valeur :")
for v in unites.values():
    print(f"\t{v:.0e}")

# On parcourt les clés et les valeurs du dictionnaire
print("Parcours par clé,valeur :")
for k, v in unites.items():
    print(f"\t{k} -> {v:.0e}")
Résultat :
Parcours par clé :
        kilo
        mega
        giga
        tera
Parcours par valeur :
        1e+03
        1e+06
        1e+09
        1e+12
Parcours par clé,valeur :
        kilo -> 1e+03
        mega -> 1e+06
        giga -> 1e+09
        tera -> 1e+12

Test de la présence d’une clé et/ou d’une valeur dans un dictionnaire

On utilise, comme dans les boucles, l’opérateur in.

Exemple :
unites = {'kilo': 1e3, 'mega': 1e6, 'giga': 1e9, 'tera': 1e12}

# Test de la présence d'une clé
cle='mega'
if cle in unites:
    print(f"Facteur associé à {cle} : {unites[cle]:.0e}")

# Ajout entrée de dictionnaire si valeur non présente
valeur=1e15
if valeur not in unites.values():
    unites["peta"] = valeur
    print(unites)

# Test de la présence d'un couple clé,valeur
cle = "giga"
valeur = 1e9
if (cle,valeur) in unites.items() :
    print(f"{cle} -> {valeur:.0e}")
Résultat :
Facteur associé à mega : 1e+06
{'kilo': 1000.0, 'mega': 1000000.0, 'giga': 1000000000.0, 'tera': 1000000000000.0, 'peta': 1000000000000000.0}
giga -> 1e+09

Suppression d’entrées d’un dictionnaire

Si on veut juste supprimer une entrée, on utilisera la syntaxe :
del dict["key"]

Si on veut supprimer une entrée mais toutefois récupérer sa valeur, on utilisera :
dict.pop(key).

Si on veut supprimer la dernière entrée d’un dictionnaire et la récupérer, on utilisera :
dict.popitem(key)

Enfin, si on souhaite supprimer l’ensemble des entrées d’un dictionnaire, on utilisera :
dict.clear()

Exemple :
unites = {'kilo': 1e3, 'mega': 1e6, 'giga': 1e9, 'tera': 1e12}

# Suppression d'une entrée
del unites["kilo"]
print(unites)
# -> Affiche : {'mega': 1000000.0, 'giga': 1000000000.0, 'tera': 1000000000000.0}

# Suppression d'une entrée du dictionnaire avec récupération de sa valeur
m = unites.pop('mega')
print(f"{m} ({type(m)})")
# -> Affiche : 1000000.0 (<class 'float'>)

# Suppression de la dernière entrée d'un dictionnaire*
g = unites.popitem()
print(f"{g:} ({type(g)})")
# -> Affiche : ('tera', 1000000000000.0) (<class 'tuple'>)

unites.clear()
print(unites)
# -> Affiche : {}

Compréhension de dictionnaire

Tout comme pour les listes, il est possible de générer des dictionnaires à l’aide de compréhensions Python.

Même si son usage est moins courant qu’avec les listes et aussi plus complexe à définir, une compréhension de dictionnaire peut s’avérer extrêmement pratique.

Exemple utilisant une compréhension de dictionnaire pour compter le nombre de 1 et de 0 dans un nombre binaire :
>>> binaryNumber="1010111"
>>> {bDigit:binaryNumber.count(bDigit) for bDigit in set(binaryNumber)}
{'1': 5, '0': 2}

Récapitulatif des propriétés des conteneurs

Conteneur Type test d’appartenance et fonction len() itérable ordonné indexable modifiable hachable[1]

list

séquentiel

oui

oui

oui

oui

oui

non

str

séquentiel

oui

oui

oui

oui

non

oui

range

séquentiel

oui

oui

oui

oui

non

oui

tuple

séquentiel

oui

oui

oui

oui

non

oui

dict

accès direct

oui

oui sur les clés

oui[2]

non

oui

non

set

ensemble

oui

oui

non

non

oui

non


1. propriété qui permet d’utiliser le conteneur comme une clé de dictionnaire ou comme élément d’un ensemble
2. à partir de Python 3.7

🞄  🞄  🞄