24 - (Bilan) Tuples
Nous avons déjà vu deux structures jouant le rôle de conteneurs indexés :
- Les strings permettant de stocker des caractères. Les strings sont immuables après création.
- Les tableaux permettant de stocker... n'importe quoi. Les tableaux sont muables en Python.
Aujourd'hui, nous allons voir en détails l'utilisation des tuples en Python.
Logiciel nécessaire pour l'activité : Thonny ou juste Python 3
Evaluation ✎ : questions -
1 - P-uplets
Nous allons voir une troisième structure de données linéaires : le p-uplet de valeurs.
Un p-uplet est une structure ordonnée :
- contenant p éléments
- (comme dans un tableau)
- indexant les éléments par un numéro
- (comme les tableaux)
- permettant de stocker des données de natures différentes
- (contrairement aux tableaux où les cases doivent toutes contenir le même type de données)
- immuable
- (contrairement aux tableaux qui sont muables)
1 - Déclaration d'un p-uplet avec des parenthèses ( ) en Python
En anglais, on dira tuple.
Les éléments délimitateurs sont les parenthèses. Chaque élément du p-uplet est séparé des autres par une virgule.
Exemple avec un p-uplet contenant le nom du produit, le nombre en stock et le prix à l'unité.
>>> exemple = ('Ananas', 60, 2.20)
>>> exemple
('Ananas', 60, 2.20)
>>> type(exemple)
<class 'tuple'>
Un seul élément ? : si vous voulez créer un p-uplet ne contenant qu'un seul élément, il faudra placer une virgule après le premier élément. Sinon, l'interpréteur Python croira qu'il s'agit juste de parenthèses.
Exemple à ne pas faire si vous voulez un tuple
>>> exemple = ('Ananas')
>>> exemple
'Ananas'
>>> type(exemple)
<class 'str'>
Exemple à faire si vous voulez un tuple
>>> exemple = ('Ananas' ,)
>>> exemple
('Ananas',)
>>> type(exemple)
<class 'tuple'>
2 - Nombre d'éléments stockés avec len
On peut utiliser la fonction native len() pour obtenir le nombre d'éléments stockés dans le p-uplet.
Exemple
>>> exemple = ('Ananas', 60, 2.20)
>>> len(exemple)
3
On peut nommer
- singleton un p-uplet de 1 élément
- couple ou doublet un p-uplet de 2 éléments
- triplet un p-uplet de 3 éléments
- quadruplet un p-uplet de 4 éléments
- quintuplet un p-uplet de 5 éléments
- ...
3 - Accès à l'un des éléments avec [indice]
Pour accéder à l'un des éléments en particulier, on utilise des crochets en y placant l'indice voulu.
Notez bien que le tuple est déclaré avec des parenthèses mais qu'on accède à ses éléments avec des crochets.
Exemple
>>> exemple = ('Ananas', 60, 2.20)
>>> exemple[0]
'Ananas'
>>> exemple[1]
60
>>> exemple[2]
2.2
>>> element_0 = exemple[0]
>>> element_0
'Ananas'
La correspondance indice - élément donne ceci sur l'exemple
Indice | 0 | 1 | 2 |
Elément | 'Ananas' | 60 | 2.2 |
On notera que l'indice du premier élément porte le numéro d'indice 0 et pas le numéro 1 !
4 - Lecture des éléments à l'aide d'une boucle et de l'indice (méthode classique)
Dans cette boucle FOR, on utilise la syntaxe in range pour obtenir successivement les différents indices.
Exemple avec un n-uplet de 14 éléments :
notes = (15, 18, 8, 10, 12, 15, 20, 5, 12, 17, 12, 10, 18, 4)
Indice | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
Elément | 15 | 18 | 8 | 10 | 12 | 15 | 20 | 5 | 12 | 17 | 12 | 10 | 18 | 4 |
4.1 - Version bisounours
1
2
3
4 | notes = (15, 18, 8, 10, 12, 15, 20, 5, 12, 17, 12, 10, 18, 4)
for i in range(14):
print(notes[i])
|
La variable de boucle i prend successivement les valeurs 0 puis 1 puis 2 ... et finalement 13.
Ce programme va afficher ceci dans la console puisqu'on demande en ligne 4 d'afficher notes[i] et pas juste i :
15
18
8
10
12
15
20
5
12
17
12
10
18
4
C'est donc comme si vous aviez tapé ceci :
1
2
3
4
5
. | notes = (15, 18, 8, 10, 12, 15, 20, 5, 12, 17, 12, 10, 18, 4)
print(notes[0])
print(notes[1])
print(notes[2])
...
print(notes[13])
|
4.2 - Version réelle
On utilisera plutôt len() pour ne pas avoir à taper directement l'indice maximum du tuple.
1
2
3
4 | notes = (15, 18, 8, 10, 12, 15, 20, 5, 12, 17, 12, 10, 18, 4)
for i in range(len(notes)):
print(notes[i])
|
4.3 - Point important à avoir en tête
- La variable i contient le numéro d'une case du n-uplet
- L'expression notes[i] correspond au contenu de la case i.
5 - Lecture directe des éléments un à un
Nous avons vu, qu'avec cette façon de faire, on n'obtient pas l'indice de la case mais directement le contenu de la case.
Dans cette boucle FOR, on note le nom du tuple directement derrière le in. Aucun range à l'horizon.
1
2
3
4
5
6
7
8
9 | notes = (15, 18, 8, 10, 12, 15, 20, 5, 12, 17, 12, 10, 18, 4)
for element in notes:
if element > 12:
print(f"Bien : {element}")
elif element < 8:
print(f"Pas bien : {element}")
else:
print(f"Moyen : {element}")
|
La variable de boucle note va alors contenir successivement tous les éléments du n-uplet. note va donc valoir d'abord 15, puis 18 au tour de boucle suivant, puis 8...
Ce programme affiche "Bien", "Pas bien" ou "Moyen" devant chaque note sans avoir à passer par un indice dans le code :
Bien : 15
Bien : 18
Moyen : 8
Moyen : 10
Moyen : 12
Bien : 15
Bien : 20
Pas bien : 5
Moyen : 12
Bien : 17
Moyen : 12
Moyen : 10
Bien : 18
Pas bien : 4
6 - Non mutabilité / Immuabilité des tuples EN PYTHON
En Python, les tuples sont non mutables (anglissisme) ou immuables (en français) : cela veut dire qu'on ne peut pas modifier le contenu après sa création. On dit qu'on ne peut pas modifier l'état du tuple placé en mémoire.
>>> produit = ('Ananas', 60, 2.2)
>>> produit[1] = 50
TypeError: 'tuple' object does not support item assignment
Nous allons voir maintenant les utilisations courantes des tuples
- Volonté de transmettre une donnée non mutable / immuable
- Création d'une fonction qui doit renvoyer plusieurs données alors qu'une fonction ne peut renvoyer qu'une seule réponse
1 - Volonté de transmettre une donnée non mutable / immuable
Voici un programme qui possède des fiches comportant 3 informations par produit :
- Un string pour son nom ('Jeu vidéo de la mort qui tue' par exemple),
- Un entier pour le nombre de produits vendus (103 par exemple) et
- Un float pour le prix d'un exemplaire du produit (20.3 par exemple)
Dans ce cas de figure, calculer le prix total consiste à calculer 103*20.3, soit 2090.9 euros.
01° Lors de l'appel de la ligne 13, que va contenir le paramètre fiche de la fonction infos_total() ? A quoi correspond alors fiche[2] ?
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | def infos_total(fiche):
"""Renvoie un tuple contenant le nom du produit et le total (nbr vendu*prix)
:: param fiche(tuple (str, int, float)) :: le nom du produit, nombre vendu et prix d'un exemplaire
:: return (tuple (str, float)) :: tuple contenant le nom du produit et le total
>>> infos_total(("Voiture électrique", 100, 5.0))
('Voiture électrique', 500.0)
"""
return ('vide', 0)
produit_1 = ('Jeu vidéo de la mort qui tue', 103, 20.3)
tot = infos_total(produit_1)
|
...CORRECTION...
L'argument envoyé est produit_1.
On a donc fiche = produit_1 = ('Jeu vidéo de la mort qui tue', 103, 20.3)
On voit que fiche[0] est évaluée à 'Jeu vidéo de la mort qui tue'.
On voit que fiche[1] est évaluée à 103.
On voit que fiche[2] est évaluée à 20.3.
02° Modifier maintenant la fonction infos_total() pour qu'elle fasse le travail qu'on lui demande de faire : calculer le total à payer pour ce type de produits achetés et envoyer un tuple (nom du produit, total à payer).
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | def infos_total(fiche):
"""Renvoie un tuple contenant le nom du produit et le total (nbr vendu*prix)
:: param fiche(tuple (str, int, float)) :: le nom du produit, nombre vendu et prix d'un exemplaire
:: return (tuple (str, float)) :: tuple contenant le nom du produit et le total
>>> infos_total(("Voiture électrique", 100, 5.0))
('Voiture électrique', 500.0)
"""
return ('vide', 0)
produit_1 = ('Jeu vidéo de la mort qui tue', 103, 20.3)
tot = infos_total(produit_1)
|
Par exemple, on envoie la fiche suivante à la fonction :
('Jeu vidéo de la mort qui tue', 10, 200.0)
La fonction va calculer 10*200.0 et renvoyer le tuple ('Jeu vidéo de la mort qui tue', 2000.0)
...CORRECTION...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | def infos_total(fiche):
"""Renvoie un tuple contenant le nom du produit et le total (nbr vendu*prix)
:: param fiche(tuple (str, int, float)) :: le nom du produit, nombre vendu et prix d'un exemplaire
:: return (tuple (str, float)) :: tuple contenant le nom du produit et le total
>>> infos_total(("Voiture électrique", 100, 5.0))
('Voiture électrique', 500.0)
"""
nom = fiche[0]
total = fiche[1] * fiche[2]
return (nom, total)
produit_1 = ('Jeu vidéo de la mort qui tue', 103, 20.3)
tot = infos_total(produit_1)
|
03° Après exécution du programme (avec la fonction correctement réalisée), que devrait afficher la console si on tape ceci ?
>>> tot[1]
???
Plus tard, quelqu'un tape l'instruction ci-dessous par erreur : alors qu'il voulait voir la valeur, il affecte une nouvelle valeur. Que va-t-il se passer ? Les données stockées dans le tuple tot sont-elles plus sécurisées qui si on avait utilisé un tableau ?
>>> tot[1] = 0
???
...CORRECTION...
La fonction a placé le total dans la case d'indice 1 du tuple de réponse, soit 2090.9
.
Par contre, l'affectation provoque une erreur : le tuple est une structure immuable en Python.
2 - Retour de plusieurs données par une fonction
Autre gros intérêt des tuples en Python : on peut faire semblant d'avoir une fonction qui renvoie plusieurs réponses ! C'est d'ailleurs ce qu'on a fait avec la marge : on renvoie bien le nom du produit et son total.
1
...
13
... | def infos_total(fiche):
...
return (nom, total)
|
Si on veut renvoyer plusieurs choses, on peut donc :
- Utiliser un conteneur de type tuple et obtenir une réponse immuable.
- Utiliser un conteneur de type tableau et obtenir une réponse muable.
Imaginons qu'on veuille créer une fonction qui renvoie les coordonnées x et y ainsi que l'angle d'une tortue de turtle. Pas possible 3 réponses à fournir. Sauf si on triche : on peut encapsuler les trois valeurs dans un tuple.
Voici la version explicite : on voit explicitement les parenthèses ligne 7 qui montrent qu'on renvoie un tuple.
1
2
3
4
5
6
7 |
|
04° Utiliser le programme suivant pour vous convaincre que la fonction renvoie bien un tuple contenant 3 informations. Le mieux est certainement de visualiser à la fois la console de Thonny et l'interface graphique Turtle.
1
2
3
4
5
6
7
8
9
10
11
12
13
14 |
|
(0.0, 0.0, 0.0)
(3.061616997868383e-15, 50.0, 90.0)
Remarque : pourquoi pas 0 pour x alors qu'on ne fait que bouger verticalement ?
La première valeur obtenue vaut 0. Mais le module fait le calcul sur des flottant. Après mouvement, on obtient un flottant valant presque zéro : 3,06e-15 veut dire 0,00000000000000306. Presque 0.
Donc, attention souvenez vous des premières activités : jamais de test d'égalité sur les flottants :
>>> 3.061616997868383e-15 == 0
False
On peut bien entendu stocker le tuple renvoyé par la fonction infos() (Ligne 13) puis récupérer individuellement les 3 éléments que la fonction a empaqueté dans sa réponse unique (L14-L15-L16).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 |
|
>>> x
3.061616997868383e-15
>>> y
50
>>> a
90
Comme on peut le voir :
- La fonction ne renvoie en réalité qu'une réponse : un 3-uplet.
- Mais on parvient à récupérer trois informations en lisant une à une les cases du 3-uplet.
Désempaquetage (unpacking en anglais)
Voyons maintenant comment faire plus simple encore.
Notre fonction renvoie une seule réponse : un tuple. Mais ce tuple comporte toujours 3 cases. Or, c'est le contenu des cases qui m'intéresse. En Python, on peut alors directement placer 3 variables devant l'opérateur d'affectation : voir la ligne 13.
1
2
3
4
5
6
7
8
9
10
11
12
13 |
|
On voit sur la ligne 13 qu'on place directement le tuple de 3 cases fournit par la fonction dans un tuple qui a déjà 3 places nommées x,y et a.
- x va automatiquement être affecté avec l'indice 0 du tuple reçu
- y va automatiquement être affecté avec l'indice 1 du tuple reçu
- a va automatiquement être affecté avec l'indice 2 du tuple reçu
>>> x
3.061616997868383e-15
>>> y
50
>>> a
90
05° La fonction renvoie-t-elle vraiment trois valeurs différentes ? Dans la fonction, où voit-on explicitement que la fonction va renvoyer un tuple ? Dans le programme, où voit-on explicitement qu'on récupère un tuple après avoir lancer l'appel de la fonction ?
...CORRECTION...
Non : la fonction renvoie un TUPLE qui contient 3 valeurs contenues aux indices 0, 1 et 2.
LINGE 07 : On voit EXPLICITEMENT des parenthèses derrière le return : le développeur indique clairement que la fonction renvoie un tuple.
LIGNE 13 : On voit EXPLICITEMENT des parenthèses autour de x, y et a : le développeur indique clairement qu'on veut récupérer un tuple lors de l'appel de la fonction.
Programmer, ce n'est pas créer un ensemble d'instructions volontairement diffile à comprendre. Ici, les parenthèses permettent à la personne qui lit le programme de voir explicitement qu'on agit sur des tuples. Voici une dernière version moins explicite qu'on voit néanmoins assez souvent :
Version implicite : pas de parenthèses aux lignes 7 (lors de l'envoi) et 13 (lors de la réception)
1
2
3
4
5
6
7
8
9
10
11
12
13 |
|
Attention avec cette version donc : on pourrait croire que la fonction renvoie 3 variables sur la ligne 7. C'est faux. La fonction renvoie bien un tuple, sauf qu'on ne l'indique pas clairement. Ca fonctionne mais c'est moins clair : il faut connaître précisement Python pour le savoir.
Bref, évitez cette façon de faire : soyez explicite.
2 - transformation de l'un en l'autre
Nous venons de voir que les structures de données ont des avantages et des inconvénients.
En Python, si on veut qu'une fonction puisse modifier les données stockées, il faut utiliser un tableau car il est mutable.
Par contre, si on veut juste fournir des valeurs, autant utiliser un tuple qui évitera ainsi que quelqu'un ne les modifie par erreur.
Mais comment passer de l'un à l'autre ?
En utilisant les fonctions natives permettant de le faire :
- str() tente de transformer les données fournies en une string (nommé str en Python)
- list() tente de transformer les données fournies en un tableau (nommé list en Python)
- tuple() tente de transformer les données fournies en un p-uplet (nommé tuple en Python)
06° Exécuter les commandes suivantes dans la console pour comprendre l'action de ces trois fonctions
En partant d'un string
>>> s = "abc"
>>> s
'abc'
>>> t = list(s)
>>> t
['a', 'b', 'c']
>>> p = tuple(s)
>>> p
('a', 'b', 'c')
En partant d'un tableau
>>> t = ['a','b','c']
['a', 'b', 'c']
>>> s = str(t)
>>> s
"['a', 'b', 'c']"
>>> p = tuple(t)
>>> p
('a', 'b', 'c')
En partant d'un tuple
>>> p = ('a','b','c')
('a', 'b', 'c')
>>> s = str(p)
>>> s
"('a', 'b', 'c')"
>>> t = list(p)
>>> t
['a', 'b', 'c']
Une dernière remarque pour la fin
Comment trouver le nombre de mots dans une phrase ?
Nous avons déjà vu d'autres façons de convertir. En utilisant par exemple la méthode split() des strings.
Cette méthode crée une liste à partir d'un string. Comment ? En divisant le string à l'aide d'un élément délimitateur. Par défaut, il s'agit de l'espace.
>>> phrase = "Voici un exemple de phrase, il permet de compter les mots."
>>> mots = phrase.split()
>>> mots
['Voici', 'un', 'exemple', 'de', 'phrase,', 'il', 'permet', 'de', 'compter', 'les', 'mots.']
>>> nbr_mots = len(mots)
>>> nbr_mots
11
Et du coup, nous pouvons en faire une fonction.
1
2
3
4 |
|
Bien entendu, il reste encore quelques détails à régler, comme supprimer les points et les virgules. Nous avions vu la méthode replace() dans l'activité où nous cherchions à trouver le sens d'un texte automatiquement si vous vous souvenez. Sinon, c'était l'actité Python nommée "Mini-projet avec FOR et IF".
>>> s = "..mots..."
>>> s.replace(".", "")
'mots'
Nous avions également vu la méthode join() qui permet de créer un string à partir des cases d'un tableau.
>>> t = ['A', 'B', 'C']
>>> "".join(t)
'ABC'
3 - Appartenance
Une autre action assez courante sur les structures de données linéaires (strings, tableaux, tuples) est de savoir si un élément appartient à la structure.
Voici une fonction appartient() qui permet de vérifier que l'élément x est contenu dans un tableau par exemple :
1
2
3
4
5
6 |
|
Comme on ne fait que lire la case, on peut utiliser également la version de la boucle où on obtient directement les valeurs :
1
2
3
4
5
6 |
|
Mais on peut faire encore plus simple car un tel mécanisme existe déjà dans Python. On utilise le mot-clé in.
1
2
3 |
|
L'expression x in tableau est donc une expression booléenne : elle est évaluée à True si x est bien un élément du tableau.
Le plus beau, c'est que cela fonctionne avec les tableaux, les tuples et les strings.
>>> t = [12, 5, 18]
>>> 5 in t
True
>>> 6 in t
False
>>> p = ("alors", "ça", "va", "?")
>>> "ça" in p
True
>>> "ca" in p
False
>>> s = "Alors, ça va ?"
>>> "Alors" in s
True
>>> "Bonjour" in s
False
07° Créer une fonction qui permet de savoir si une séquence ADN particulière (une simple chaîne de caractères pour l'informaticien) se trouve dans ADN (une autre chaîne de caractères pour l'informaticien). Tester alors votre fonction avec la recherche de "TATTTAAATTATTTC" dans la chaîne suivante. Vous devriez trouver qu'elle est présente. Mais à la main, ca risque d'être long...
ADN = "CAACTCCTAAGCCAGTGCCAGAAGAGCCAAGGACAGGTACGGCTGTCATCACTTAGACCTCACCCTGTGGAGCCACACCCTAGGGTTGGCCAATCTACTCCCAGGAGCAGGGAGGGCAGGAGCCAGGGCTGGGCATAAAAGTCAGGGCAGAGCCATCTATTGCTTACATTTGCTTCTGACACAACTGTGTTCACTAGCAACCTCAAACAGACACCATGGTGCACCTGACTCCTGAGGAGAAGTCTGCCGTTACTGCCCTGTGGGGCAAGGTGAACGTGGATGAAGTTGGTGGTGAGGCCCTGGGCAGGTTGGTATCAAGGTTACAAGACAGGTTTAAGGAGACCAATAGAAACTGGGCATGTGGAGACAGAGAAGACTCTTGGGTTTCTGATAGGCACTGACTCTCTCTGCCTATTGGTCTATTTTCCCACCCTTAGGCTGCTGGTGGTCTACCCTTGGACCCAGAGGTTCTTTGAGTCCTTTGGGGATCTGTCCACTCCTGATGCTGTTATGGGCAACCCTAAGGTGAAGGCTCATGGCAAGAAAGTGCTCGGTGCCTTTAGTGATGGCCTGGCTCACCTGGACAACCTCAAGGGCACCTTTGCCACACTGAGTGAGCTGCACTGTGACAAGCTGCACGTGGATCCTGAGAACTTCAGGGTGAGTCTATGGGACCCTTGATGTTTTCTTTCCCCTTCTTTTCTATGGTTAAGTTCATGTCATAGGAAGGGGAGAAGTAACAGGGTACAGTTTAGAATGGGAAACAGACGAATGATTGCATCAGTGTGGAAGTCTCAGGATCGTTTTAGTTTCTTTTATTTGCTGTTCATAACAATTGTTTTCTTTTGTTTAATTCTTGCTTTCTTTTTTTTTCTTCTCCGCAATTTTTACTATTATACTTAATGCCTTAACATTGTGTATAACAAAAGGAAATATCTCTGAGATACATTAAGTAACTTAAAAAAAAACTTTACACAGTCTGCCTAGTACATTACTATTTGGAATATATGTGTGCTTATTTGCATATTCATAATCTCCCTACTTTATTTTCTTTTATTTTTAATTGATACATAATCATTATACATATTTATGGGTTAAAGTGTAATGTTTTAATATGTGTACACATATTGACCAAATCAGGGTAATTTTGCATTTGTAATTTTAAAAAATGCTTTCTTCTTTTAATATACTTTTTTGTTTATCTTATTTCTAATACTTTCCCTAATCTCTTTCTTTCAGGGCAATAATGATACAATGTATCATGCCTCTTTGCACCATTCTAAAGAATAACAGTGATAATTTCTGGGTTAAGGCAATAGCAATATTTCTGCATATAAATATTTCTGCATATAAATTGTAACTGATGTAAGAGGTTTCATATTGCTAATAGCAGCTACAATCCAGCTACCATTCTGCTTTTATTTTATGGTTGGGATAAGGCTGGATTATTCTGAGTCCAAGCTAGGCCCTTTTGCTAATCATGTTCATACCTCTTATCTTCCTCCCACAGCTCCTGGGCAACGTGCTGGTCTGTGTGCTGGCCCATCACTTTGGCAAAGAATTCACCCCACCAGTGCAGGCTGCCTATCAGAAAGTGGTGGCTGGTGTGGCTAATGCCCTGGCCCACAAGTATCACTAAGCTCGCTTTCTTGCTGTCCAATTTCTATTAAAGGTTCCTTTGTTCCCTAAGTCCAACTACTAAACTGGGGGATATTATGAAGGGCCTTGAGCATCTGGATTCTGCCTAATAAAAAACATTTATTTTCATTGCAATGATGTATTTAAATTATTTCTGAATATTTTACTAAAAAGGGAATGTGGGAGGTCAGTG"
4 - FAQ
Rien pour l'instant
5 -
6 -
Activité publiée le 19 03 2022
Dernière modification : 07 09 2024
Auteur : ows. h.