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 : 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) 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é. 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 🞄 🞄 🞄 Présentation de Python Liens Python