backend / python
Tuples et sets
Explication
Ce que vous allez apprendre
- Choisir entre liste, tuple et set selon qu'on a besoin de mutabilité, d'ordre ou d'unicité
- Comprendre pourquoi un tuple peut servir de clé de dictionnaire et une liste jamais
- Utiliser les opérations d'ensembles (
|,&,-,^) pour comparer deux collections - Dédupliquer une liste en préservant ou non l'ordre d'origine
- Éviter les deux pièges d'écriture les plus fréquents :
{}et le tuple à un seul élément
Dans quel contexte ?
Un développeur doit comparer les permissions de deux utilisateurs, stockées dans des listes permissions_a = ["lire", "ecrire"] et permissions_b = ["lire", "supprimer"], pour savoir lesquelles sont communes. Convertir ces listes en set permet d'écrire directement set(permissions_a) & set(permissions_b) plutôt que d'imbriquer deux boucles for — un exemple typique où choisir la bonne structure de données simplifie radicalement le code.
Deux façons de refuser la mutation, et une de refuser les doublons
Après les listes, mutables et flexibles, voici deux structures qui imposent des contraintes volontaires. Un tuple est une séquence ordonnée qu'on ne peut plus modifier une fois créée : c'est un choix de conception, pas une limitation technique. On l'utilise pour représenter des données qui vont ensemble et ne doivent pas changer, comme des coordonnées (x, y). Un set, lui, est une collection sans ordre garanti où chaque valeur ne peut apparaître qu'une seule fois.
| Structure | Ordonnée | Mutable | Doublons |
|---|---|---|---|
list | Oui | Oui | Autorisés |
tuple | Oui | Non | Autorisés |
set | Non | Oui | Interdits |
frozenset | Non | Non | Interdits |
Pourquoi l'immuabilité est un avantage, pas une contrainte
Un objet immuable comme un tuple peut être utilisé comme clé de dictionnaire ou stocké dans un set, ce qu'une liste ne permet jamais. C'est parce que Python a besoin de garantir qu'un objet hashable ne changera jamais de valeur après avoir été inséré dans une structure de hachage : sinon, il serait impossible de le retrouver. Cette propriété rend les tuples précieux pour représenter des identifiants composites ou des clés de cache.
La rapidité cachée des sets
Un set n'est pas une simple liste sans doublons : en interne, il repose sur une table de hachage, exactement comme un dictionnaire. Résultat, tester si un élément est dans l'ensemble prend un temps quasi constant, alors que la même opération sur une liste doit potentiellement parcourir tous les éléments. Sur de gros volumes, la différence de performance est considérable.
Bonne pratique
Si votre code teste souvent if valeur in ma_collection: sur une grosse collection, convertissez-la en set dès sa création plutôt qu'à chaque test : ids_valides = set(charger_ids()) puis if id_recu in ids_valides: reste rapide même avec un million d'entrées.
Un piège d'écriture fréquent
{} crée un dictionnaire vide, jamais un set vide : il faut écrire set() explicitement. Et un tuple à un seul élément nécessite une virgule finale, (42,), sinon Python ne voit qu'une simple expression entre parenthèses.
Piège fréquent
mon_set = {} ne crée pas un set vide mais un dictionnaire vide (type({}) vaut dict). Il faut écrire explicitement mon_set = set().
Commandes & code
Tuples et sets
Tuples : sequences immuables. Sets : collections non ordonnees sans doublons.
# --- Tuples ---
point = (3, 4)
point_sans_parentheses = 3, 4 # les parentheses sont optionnelles
singleton = (42,) # la virgule est OBLIGATOIRE pour un tuple a 1 element
vide = ()
x, y = point # unpacking
premier, *reste = (1, 2, 3, 4) # unpacking etendu -> premier=1, reste=[2,3,4]
# Les tuples sont immuables : impossible de modifier un element
try:
point[0] = 99
except TypeError as e:
print(f"Erreur : {e}")
# Mais un tuple peut contenir des objets mutables
t = ([1, 2], "texte")
t[0].append(3) # OK : on mute la liste, pas le tuple
print(t)
# Utilite : les tuples sont hashables (si leur contenu l'est) -> cles de dict
cache = {}
cache[(1, 2)] = "resultat pour (1,2)"
# namedtuple : tuple avec des champs nommes, leger et immuable
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y, p[0])
# --- Sets ---
fruits = {"pomme", "banane", "cerise"}
vide_set = set() # {} cree un dict vide, pas un set !
fruits.add("kiwi")
fruits.discard("banane") # ne leve pas d'erreur si absent
fruits.remove("cerise") # leve KeyError si absent
# Operations mathematiques d'ensembles
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b) # union : {1,2,3,4,5,6}
print(a & b) # intersection : {3,4}
print(a - b) # difference : {1,2}
print(a ^ b) # difference symetrique : {1,2,5,6}
print(a <= {1, 2, 3, 4, 5}) # sous-ensemble ? True
# Deduplication rapide en preservant les elements uniques (mais pas l'ordre)
doublons = [1, 2, 2, 3, 3, 3, 4]
uniques = list(set(doublons))
# Deduplication en preservant l'ordre (Python 3.7+, dict garde l'ordre d'insertion)
uniques_ordonnes = list(dict.fromkeys(doublons))
# frozenset : version immuable et hashable d'un set
constantes = frozenset({1, 2, 3})
config_cache = {frozenset({"a", "b"}): "resultat"} # frozenset comme cle de dict
# Test d'appartenance : O(1) en moyenne pour un set, O(n) pour une liste
grand_ensemble = set(range(1_000_000))
print(999_999 in grand_ensemble) # quasi instantaneRésumé
- Un tuple à un élément nécessite la virgule :
(42,). - Les tuples sont immuables donc hashables : utilisables comme clés de
dict. - Un
setteste l'appartenance en O(1) en moyenne, contre O(n) pour unelist. frozensetest la version immuable et hashable d'unset.
Exercices pratiques
Mission : accélérer un contrôle de permissions trop lent
Objectif : Remplacer une double boucle par des opérations d'ensembles pour comparer des permissions, et corriger un piège d'écriture sur un set vide.
Contexte
Un système de permissions compare deux listes, permissions_utilisateur et permissions_requises, avec une double boucle for imbriquée pour trouver les permissions manquantes. Sur un utilisateur avec des milliers de permissions, l'appel devient perceptiblement lent, et un développeur a par erreur initialisé un "ensemble de permissions bloquées" avec bloquees = {}.
Tu dois remplacer la double boucle par des opérations d'ensembles, corriger l'initialisation du set vide, et expliquer pourquoi les tuples restent utilisables comme clés de cache alors que les listes ne le sont jamais.