Skip to content

1 2 2 1 Fiche Puplets

Les p-uplets

Introduction

Un p-uplet (appelé également N-uplet) est une succession de valeurs indexées par des entiers. La première valeur a l'indice 0, la deuxième l'indice 1, etc. On parle parfois de composantes pour désigner les différentes valeurs qui constituent le p-uplet. Le terme p-uplet est construit comme une généralisation des mots français triplet (3-uplet), quadruplet (4-uplet) etc.

En Python, l'objet p-uplet s'appelle tuple et c'est le terme que nous emploierons désormais. Il s'agit du troisième objet séquence que nous voyons après les chaînes de caractères (str) et les listes (list).

Les valeurs peuvent être de types variés. Voici un exemple de tuple contenant trois valeurs respectivement des types int, bool et str :

123, True, 'Un triplet'

On pourra ajouter des parenthèses pour améliorer la lisibilité dans certains cas et pour les tuple imbriqués :

((1, 2, 3), (4, 5))

Le tuple vide et le singleton sont un peu particuliers (notez la virgule à la fin du singleton) :

>>> vide = ()
>>> single = 'seul',

Dans le cas du singleton, et pour des débutants, il sera peut-être souhaitable d'ajouter les parenthèses :

>>> single = ('seul',)

Ci-dessous quelques exemples avec ou sans parenthèses (certaines utilisations sont avancées, essayez de repérer les tuple) :

Exemples d'utilisations

Exemple 1

def signature(identite):
    nom, prenom = identite
    return f'{prenom[0].upper()}.{nom.capitalize()}'

>>> mon_pere = ('vador', 'dark')
>>> print(signature(mon_pere))
D.Vador

Exemple 2

def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a+b
    return a

Exemple 3

EST = (1, 0)
SUD = (0, 1)
OUEST = (-1, 0)
NORD = (0, -1)

def explorer(x, y):
    for dx, dy in (EST, SUD, OUEST, NORD):
        x = x + dx
        y = y + dy
        traiter(x, y)

Exemple 4

def compter(mot):
    nb_voyelles = 0
    nb_consonnes = 0
    for c in mot:
        if c in 'aeiouy':
            nb_voyelles += 1
        else:
            nb_consonnes += 1
    return nb_voyelles, nb_consonnes

Nous reviendrons plus tard sur ces exemples. Continuons notre introduction des tuple.

Construire un tuple

Par extension

Comme pour les list, en listant les éléments :

>>> sports = 'badmington', 'escalade', 'athlétisme', 'natation'

Par concaténation multiple

Elle n'a pas grand intérêt puisque le tuple n'autorise pas la modification de ses composantes, une affectation de ce genre condamnerait à ne manipuler que la même valeur :

>>> multiple = (0, ) * 5
>>> multiple
(0, 0, 0, 0, 0)

Par la fonction tuple

>>> tuple('abc')
('a', 'b', 'c')

>>> tuple([1, 2, 3, 4])
(1, 2, 3, 4)

Par compréhension

>>> tuple(x ** 2 for x in range(1, 11))
(1, 4, 9, 16, 25, 36, 49, 64, 81, 100)

Attention

>>> t = (x ** 2 for x in range(1, 11))

ne fournit pas un tuple mais une expression génératrice.

>>> type(t)
generator

Cette expression génère un itérateur utilisable une fois. Ce sont ces expressions qui sont à l'origine de toutes les constructions en compréhension.

>>> for x in t:
        print(x, end=' ')
    print('fin')
1 4 9 16 25 36 49 64 81 100 fin

Mais pas de deuxième passage :

>>> for x in t:
        print(x, end=' ')
    print('fin')
fin

Manipulation de base des tuple

Accès aux valeurs

L'opérateur crochet :[ ] nous permet d'accéder à une valeur dont on donne l'indice :

t_exemple = 123, True, 'Un triplet'

Dès lors t_exemple[0] référence l'objet 123, t_exemple[1] référence True, ainsi de suite. Comme pour toute séquence, nous pouvons fournir des indices négatifs pour référencer nos valeurs en partant de la dernière : t_exemple[-1]...

Modification des valeurs

Ce paragraphe sera très court : on ne peut pas changer les valeurs d'un tuple. On dit que le tuple est un objet non modifiable ou immuable en français. Le terme anglais est immutable et on emploie aussi en français l'adjectif non mutable. Tenter de modifier la composante d'un tuple provoque une TypeError :

>>> t_exemple[0] = 42
----> 1 t_exemple[0] = 42

TypeError: 'tuple' object does not support item assignment

La longueur

Comme pour les autres séquences : la fonction len donne la longueur du tuple soit son nombre d'éléments :

>>> len((1, 2, 3, 4))
4

Diverses utilisations

Comme itérable d'une boucle for

Comme dans l'exemple 3, il peut arriver qu'on doive parcourir plusieurs valeurs. On pourrait les mettre dans une list. Si on ne change jamais les valeurs, l'utilisation d'un tuple à la place de la list est une bonne idée, par souci d'efficacité (moins de place en mémoire par exemple).

for direction in (EST, SUD, OUEST, NORD):
    # traitement

Le décompactage

Toujours dans un souci de lisibilité du code, il est souvent intéressant de nommer les composantes d'un tuple t plutôt que d'utiliser t[i]. Cette remarque est d'ailleurs à l'origine des p-uplets nommés que nous évoquons plus tard.

Supposons que nous manipulions des triplets contenant le nom d'un langage de programmation, le prénom et le nom de son inventeur. Le décompactage nous permet de récupérer les valeurs avec des noms de variables explicites :

>>> python = ('python', 'guido', 'van rossum')
>>> langage, prenom, nom = python

Avec, comme souvent, une utilisation dans une boucle. En supposant que la variable langages référence un tuple ou une list de triplets comme celui de python ci-dessus on peut écrire :

for langage, prenom, nom in langages:
    # traitement

Il faut bien entendu s'assurer que le nombre de variables est en adéquation avec le nombre de composantes, sinon on obtient une ValueError très explicite :

>>> a, b = (1, 2, 3)
----> 1 a, b = (1, 2, 3)
ValueError: too many values to unpack (expected 2)
>>> a, b, c = (1, 2)
----> 1 a, b, c = (1, 2)
ValueError: not enough values to unpack (expected 3, got 2)

On pourra utiliser * pour attraper les autres valeurs qu'on n'a pas besoin de nommer ou dont on ne connait pas le nombre exact :

premier, *autres = mon_tuple_a_decompacter

L'affectation multiple

On la rencontre souvent car elle est bien pratique. Prenons par exemple dans la fonction fibonacci l'initialisation de nos deux variables sur les valeurs 0 et 1 :

a, b = 0, 1

Il s'agit en fait d'un décompactage du tuple (0, 1) sur les variables a et b. Et cette technique est à nouveau utilisée dans le corps de la boucle pour avancer dans les éléments de la suite, de deux en deux :

a, b = b, a+b

Cela semble un peu magique et même certains pensent peut-être : mais les tuples sont immuables... ici pas de violation de la règle. Il ne s'agit pas de réaffecter une des composantes mais bien d'effectuer un décompactage. Les choses se font en deux temps (du moins en théorie) :

  1. création du tuple (b, a+b) c'est-à-dire dont la première composante est constituée de la valeur référencée par b et la deuxième est le résultat de la somme des valeurs référencées respectivement par a et b.
  2. décompactage de ce tuple pour affecter les variables a et b.

Retourner plusieurs valeurs

Une fonction ne peut pas retourner plusieurs choses. Dans tous les cas, un tuple sera renvoyé. C'est le cas de notre exemple avec la fonction compter : un tuple de deux entiers est la valeur de retour de la fonction. On retrouve cette technique dans les deux algorithmes de tri récursifs : tri par fusion et tri rapide. Dans les deux cas, une fonction permettra de couper le tableau en deux afin de lancer le tri sur chacun des sous-tableaux.

Des fonctions avec un nombre variable de paramètres

Cette utilisation est compètement hors programme 1re NSI. Supposons que nous voulions écrire une fonction somme qui permette de sommer les entiers qu'on lui passe, comme ceci :

>>> somme(1, 2)
3
>>> somme(1, 2, 3)
6
>>> somme(1)
1
>>> somme()
0

On pourra définir somme comme ceci :

def somme(*args):
    total = 0
    for e in args:
        total += e
    return total

Dès lors, dans la fonction, args est un tuple des arguments passés à l'appel. La fonction prédéfinie sum de Python accepte en paramètre un tuple ou une list. On pourrait donc définir notre fonction somme de cette façon :

def somme(*args):
    return sum(args)

L'emploi du nom args est une convention.

Manipulations avancées

Compter les occurrences

La méthode count, disponible pour toute séquence, permet de compter les occurrences d'une valeur dans le tuple.

>>> (1, 2, 2, 1, 1).count(1)
3

La concaténation

On peut concaténer deux tuple (+) ou plusieurs fois le même (*) pour créer de nouveaux tuple :

>>> son = ('ha',)
>>> rire_court = son + son
>>> print(rire_court)
('ha', 'ha')
>>> rire_long = son * 5
>>> print(rire_long)
('ha', 'ha', 'ha', 'ha', 'ha')

Le découpage en tranches

Le slicing pour récupérer un sous-tuple :

>>> semaine = ('lun', 'mar', 'mer', 'jeu', 'ven', 'sam', 'dim')
>>> week_end = semaine[5:]
>>> print(week_end)
('sam', 'dim')

Le caractère non modifiable des tuple rend le slicing plus simple que sur les listes. En effet, ici pas d'affectation par tranche de ce genre-là :

>>> semaine[5:] = ()
----> 1 semaine[5:] = ()

TypeError: 'tuple' object does not support item assignment

Les p-uplets nommés

L'idée consiste à pouvoir accéder à une composante du tuple non pas avec son indice entier mais grâce à un nom explicite. Nous avons déjà évoqué cette idée lors du décompactage. Dans les modules de base de Python, il n'y a pas de tuples nommés qui seront traités par des dictionnaires (voir notre chapitre suivant).

Il existe toutefois un objet du module collections : le namedtupled. Voici l'exemple tiré de la documentation officielle de Python :

from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(11, y=22)     # instantiate with positional or keyword arguments
>>> p[0] + p[1]             # indexable like the plain tuple (11, 22)
33
>>> x, y = p                # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y               # fields also accessible by name
33
>>> p                       # readable __repr__ with a name=value style
Point(x=11, y=22)

On constate que p référence un namedtuple Point qui comporte deux composantes. La première est nommée x et la deuxième y. Cela se rapproche beaucoup des propriétés de la programmation orientée objet dont une ébauche sera vue dans le programme de NSI Terminale. Nous ne développerons pas plus cet objet namedtuple : il peut être remplacé par les dictionnaires.