Python : l’essentiel sur les tuples
Si je vous propose une série d’articles sur les structures de données en Python, il faudrait vous parler des tuples ou n-uplets en bon français.
Les n-uplets sont un type de base, on les apprends à peu près en même temps que les listes. Un débutant en crée rarement mais qu’on le veuille ou non, on en récupère souvent. Retourner plusieurs données par une fonction par exemple retourne un n-uplet.
En tant que débutant, on accepte les n-uplets mais on comprends rarement à quoi sert ce type, pourquoi, pourquoi un n-uplet plutôt qu’une liste ?
Dans cet article, je vais aborder l’essentiel sur les n-uplets.
Tuple ou n-uplet ?
Avant de commencer, un paragraphe pour préciser les termes. Tuple et uplet signifient la même chose, en mathématiques, il s’agit d’une collection ordonnée finie d’objets et la notion de n-uplet signifie qu’il contient n éléments. La différence est que tuple est le terme anglais et n-uplet le français.
En français, nous devrions utiliser le terme n-uplet. La documentation Python en français fait l’effort de traduction. Cependant, dans le langage courant, on a tendance à garder le mot anglais car la majorité des informations est en anglais.
Je fais l’effort à l’écrit français d’utiliser le terme français mais pour un post de blog, il faut aussi penser indexation… C’est la raison pour laquelle je placerai aussi le mot anglais.
Créer un tuple
Vous avez certainement appris qu’un n-uplet est créé de manière similaire à une liste mais en utilisant des parenthèses au lieu des crochets. Ainsi, dans le code suivant, en ligne 1 je crée une liste, en 2, un n-uplet.
my_quizz = ["Meaning of life", 42] my_quizz = ("Meaning of life", 42)
Mais ce n’est pas tout à fait exact…
Vous allez vous en rendre compte à la création du tuple singleton, un n-uplet qui ne contient qu’une seule valeur :
my_value = (42) print(type(my_value))
Vous affichera que vous avez un entier int
… Et oui, les parenthèses sont en fait des symboles de priorité et non de type. Alors qu’est ce qui fait un n-uplet ? Tout simplement la virgule. Pour déclarer un n-uplet, il faut utiliser simplement une virgule.
my_value = (42,) print(type(my_value))
Maintenant vous avez bien un tuple
. Et en fait, vous n’avez pas besoin des parenthèses. Les déclarations de n-uplets précédentes peuvent s’écrire :
my_value = 42, my_quizz = "Meaning of life", 42 my_quizz = "Meaning of life", 42,
Les lignes 2 et 3 du code précédent sont exactement les mêmes. Je vous ai juste indiqué les deux syntaxes possibles.
Alors je sais, ça pique un peu de voir une virgule en fin de ligne. On a une impression de truc pas fini. Mais c’est un code valide.
Et sachant ceci, ça va vous expliquer quelques petites choses comme le retour de plusieurs valeurs dans une fonction :
def ma_func(): return "meaning of life", 42
Vous comprenez maintenant que la fonction ne transforme pas votre retour un n-uplet : vous avez vous-même déclaré un n-uplet pour le retour de cette fonction.
Un type immuable
Les tutos de base sur le n-uplets enseignent que le n-uplet est un type immuable. Immuable signifie que vous ne pouvez pas le modifier. Ainsi, pour un n-uplet
- Vous ne pouvez pas ajouter une donnée
- Vous ne pouvez pas supprimer une donnée
- Vous ne pouvez pas remplacer une donnée
Attention pour ce dernier point : ne pas remplacer la donnée signifie que vous ne pouvez pas affecter une nouvelle donnée à un indice de n-uplet existant. Vous pouvez modifier l’état d’une données mutable dans un n-uplet (ajouter un élément à une liste contenue dans le n-uplet par exemple).
Un type performant
La conséquence de l’immuabilité des n-uplets font qu’ils sont rapides. Sous ipython, j’ai fait un %timeit et ai obtenu le résultat suivant pour la création d’une liste et d’un n-uplet :
>>> %timeit [45, 25, 2, 12, 0] 26.8 ns ± 0.146 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each) >>> %timeit (45, 25, 2, 12, 0) 4.13 ns ± 0.0263 ns per loop (mean ± std. dev. of 7 runs, 100,000,000 loops each)
27 ns contre 4 ns… Pour la création de ce type de 5 valeurs, il faut donc presque 7 fois plus de temps pour créer une liste par rapport à un n-uplet.
Bien évidemment, le gain est infime pour une donnée mais j’ai repris ici le cas des données de l’explosion de l’article des explosions dans les jeux vidéos. Une explosion, c’est 100 particules donc 100 collections de ce type. Et vous pouvez évidemment avoir plusieurs explosions (donc plusieurs centaines de collections). Avec le besoin de rapidité pour générer une image afin d’avoir un bon frame rate, le n-uplet est le type évident à utiliser.
On peut faire la même chose pour la consultation. Essayons l’unpacking (le déballage) :
>>> %timeit x, y, vx, vy, age = [45, 25, 2, 12, 0] 32.4 ns ± 0.135 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each) >>> %timeit x, y, vx, vy, age = 45, 25, 2, 12, 0 16.3 ns ± 0.0518 ns per loop (mean ± std. dev. of 7 runs, 100,000,000 loops each)
La différence n’est pas aussi spectaculaire que la création mais elle est toujours significative.
L’explication de la performance des n-uplets tient dans leur implémentation. Les listes stockent les données dans deux blocs de mémoire, un à taille fixe et l’autre variable pour la donnée. Les n-uplets dans un seul.
Cas d’usage des tuples
La question qu’on tous les débutants en Python (et… Pas que les débutants) est quand utiliser les n-uplets ? Si on reprends la partie précédente, ce devrait être quand on a besoin de performance pour construire et/ou parcourir une collection immuable. Et ce cas coule de source.
N-uplets et listes sont en Python des séquences d’objets. Ceci veut dire que vous pouvez stocker des types différents dans ces séquences. Mais dans la pratique, l’usage des listes sera plutôt de stocker des types de données uniformes et celui des n-uplets des types potentiellement hétérogènes. Le n-uplet représentera en lui-même une donnée complexe. Une fiche de quizz par exemple est représentée par une question et une réponse, dans mon exemple ci-dessus, le n-uplet est le plus adapté.
Et c’est ce qui se passe dans la pratique. Si vous prenez le code à propos des explosions, les particules sont représentées sous forme d’un n-uplet mais sont stockées dans une liste. Ok, l’exemple n’est pas très bien choisi car le n-uplet ne contient que des réels mais la logique est bien là : la particule est une information (composée de plusieurs informations) et l’exposition est une liste d’informations.
Notez que globalement, les fonctions qui retournent des données « complexes » retourneront des n-uplet. C’est le cas des méthodes d’accès aux données de l’API des bases de données par exemple.
En conclusion
Les n-uplets ou tuples sont donc des séquences plus performantes que les listes. Elles sont destinée à contenir des éléments de types différent qui composent par leur ensemble une information. C’est donc un type adapté à la manipulation d’une quantité importante d’information qui ne nécessite pas de modification.
Cependant, si vous devez accéder plusieurs fois aux champs de la séquence, l’écriture par indice peut-être pénible. On peut alors songer aux namedtuple présentés dans l’article précédent. Mais qu’en est-il de la performance ? Ce sera le sujet d’un article dédié.
Si vous avez aimé ce post, n’hésitez pas à laisser un commentaire ci-dessous ou sur la page Facebook 😉
À propos de... Darko Stankovski
iT guy, photographe et papa 3.0, je vous fais partager mon expérience et découvertes dans ces domaines. Vous pouvez me suivre sur les liens ci-dessous.
1 réponse
[…] passant, si vous combinez cette fonctionnalité avec la déclaration de n-uplets (tuples), vous avez de quoi démystifier l’instruction d’inversion de contenu de variables en […]