Tuples et déstructuration

Création

Les tuples sont le premier conteneur que l’on va voir en python. Ils permettent ainsi une référence direct à plusieurs objets simultanément. Syntaxiquement, il s’agit d’une suite d’objets python séparés par des virgules et entourés par des parenthèses:

>>> (1, 2, 3)
(1, 2, 3)

On peut bien sûr associer une variable à un tuple:

>>> x = (1, 2, 3)
>>> x
(1, 2, 3)

PIEGE pour créer un tuple avec un seul élément il faudra quant même mettre une virgule:

>>> x = (1)
>>> x
1
>>> type(x)
<class 'int'>
>>> y = (1,)
>>> y
(1,)
>>> type(y)
<class 'tuple'>

Accès aux éléments

Le premier constat à faire est qu’un tuple est une collection ordonnée d’objets python. On va accéder aux éléments séparément en utilisant leur positionnement.

ATTENTION cependant, on va plutôt parler d’indice que de position. En effet, suivant la convention héritée de précédents langages, on commence à 0. De la même façon que 0 est le premier nombre entier.

On pourra consulter ceci pour un argumentaire (par un des pères fondateurs de l’informatique: Edsger Dijkstra) justifiant le fait de commencer à 0 l’indiçage. Il justifie aussi le fait que, comme on le verra plus tard, le slicing ne contient pas le dernier indice.

La syntaxe formelle est alors le nom du tuple suivi de [ de l’indice et de ]:

>>> y = (10, 15, -5)
>>> y
(10, 15, -5)
>>> y[0]
10
>>> y[1]
15
>>> y[2]
-5

Suivant cet exemple 10 est l’élément d’indice 0, 15 est d’indice 1 et -5 d’indice 2.

Structure hétérogène

Les éléments d’un tuple peuvent tout à fait être de natures différentes:

>>> u = (1,)
>>> u
(1,)
>>> v = (2, u)
>>> v
(2, (1,))
>>> w = (3, u, v)
>>> w
(3, (1,), (2, (1,)))
>>> x = (4, u, v, w)
>>> x
(4, (1,), (2, (1,)), (3, (1,), (2, (1,))))
>>> type(x)
<class 'tuple'>
>>> type(x[0])
<class 'int'>
>>> type(x[1])
<class 'tuple'>

Structure Statique

Une fois formé un tuple ne pourra plus évoluer: on ne peut pas changer des éléments interne, on ne peut pas non plus en rajouter ni en enlever:

>>> x = (1, 2, 3)
>>> x[1] = 4
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

La structure de conteneur dynamique sera vue plus tard, il s’agit des list. Elles sont bien sûr beaucoup plus versatile, mais c’est souvent pour bénéficier des contraintes des tuples qu’on choisit ces derniers. Cela garantit des invariants qui rendent la compréhension d’un programme plus simple.

ATTENTION en fait il y a un cas de figure (qu’on cherchera surtout à éviter) où un tuple pourra changer. C’est lorsqu’un de ses éléments mute. On reviendra plus tard là dessus quand on parlera de mutation.

Déstructuration

Il s’avère en fait que les accès aux éléments d’un tuple par leurs indices sont moins utilisés que ce à quoi on pourrait s’attendre. La méthode alternative est de déstructurer ce tuple en affectant séparément le contenu:

>>> annees = (476, 1453, 1789)
>>> annees
(476, 1453, 1789)
>>> (fin_empire_romain, chute_constantinople, revolution_francaise) = annees
>>> fin_empire_romain
476
>>> chute_constantinople
1453
>>> revolution_francaise
1789
>>> annees
(476, 1453, 1789)

ATTENTION constater bien que le fait de déstructurer le tuple ne l’a pas fait disparaître pour autant.

Noter que la coutume en python est en fait d’omettre les parenthèses lors de la déstructuration. On écrirait donc plutôt:

>>> fin_empire_romain, chute_constantinople, revolution_francaise = annees

On utilise en fait souvent cette façon de faire pour affecter simultanément plusieurs variables:

>>> x, y, z = 1, 2, 3
>>> x
1
>>> y
2
>>> z
3

Le tuple est alors implicite

Mentionnons maintenant une convention python importante. Lorsque seul une partie du tuple nous importe, le nom de variable choisie pour les parties inutiles est alors toujours _. C’est une façon de signaler au lecteur du code qu’il n’est pas la peine de s’intéresser à cette partie pour la suite:

>>> annees = (476, 987, 1453, 1789)
>>> _, sacre_hugues_capet, _, revolution_francaise = annees
>>> duree_dynastie_capetienne = revolution_francaise - sacre_hugues_capet
>>> duree_dynastie_capetienne
802

Finissons maintenant par mentionner une syntaxe de déstructuration avancée. Par exemple si on veut récupérer le premier et dernier éléments d’un tuple de taille 3 on ferait:

>>> triplet = (1, 2, 3)
>>> premier, _, dernier = triplet
>>> premier
1
>>> dernier
3

Mais si le tuple était de longueur 4, il faudrait insérer un autre _, et ainsi de suite. Et si on ne connait pas la taille du tuple on ne peut carrément pas utiliser cette syntaxe:

>>> mon_tuple = (1, 2, 3, 4, 5)
>>> premier, milieu, dernier = mon_tuple
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 3)

En fait l’opérateur * permet de signaler qu’une des variables est un conteneur est absorbe le trop plein:

>>> mon_tuple = (1, 2, 3, 4, 5)
>>> premier, milieu, dernier = mon_tuple
>>> premier, *milieu, dernier = mon_tuple
>>> premier
1
>>> dernier
5
>>> milieu
[2, 3, 4]

ATTENTION les crochets [] en lieu des parenthèses () indique que le conteneur obtenu est une list et pas un tuple.

REMARQUE la déstructuration s’applique en fait à de nombreux autres conteneurs que les seuls tuples. C’est une syntaxe extrêmement utilisée en python courant et donc à maitriser.

Exercices

  1. Reprenez encore une fois l’exercice sur les décimales de 121\frac{1}{21} en utilisant ce qu’on a vu pour obtenir un code plus concis.
  2. Reprenez l’exercice demandant de permuter le contenu de deux variables en prenant en compte les possibilités vues ci-dessus.
  3. Essayer la fonction len sur différents tuples que semble-t-elle faire?

Pour aller plus loin

On pourra essayer

>>> help("TUPLES")

pour voir la documentation sur la structure de donnée. Attention on a aussi des informations sur les listes (list) et les range qui forment à trois les sequence de python.

On regardera

>>> help("TUPLELITERALS")

pour voir la grammaire de création de tuple.

On pourra regarder de nouveau

>>> help("ASSIGNMENT")

pour voir toutes les possibilités de déstructuration. (En particulier la partie décrivant target_list)

Corrections

  1. >>> restant0 = 1
    >>> (decimale1, restant1) = divmod(10 * restant0, 21)
    >>> (decimale2, restant2) = divmod(10 * restant1, 21)
    >>> (decimale3, restant3) = divmod(10 * restant2, 21)
    >>> (decimale4, restant4) = divmod(10 * restant3, 21)
    >>> decimale1, decimale2, decimale3, decimale4
    (0, 4, 7, 6)
  2. C’est une astuce très classique en python dont on pourra régulièrement tirer profit.

    >>> a, b = 10, 25
    >>> a
    10
    >>> b
    25
    >>> a, b = b, a
    >>> a
    25
    >>> b
    10
  3. len est l’abréviation de length. La fonction renvoie le nombre d’éléments du tuple, comme on le voit ci-dessous. Là encore elle accepte tout type de conteneur.

    >>> u = (1,)
    >>> len(u)
    1
    >>> x = (1, 2)
    >>> len(x)
    2
    >>> y = (1, 2, 3)
    >>> len(y)
    3