Unpacking

L'unpacking est un terme anglais que l'on pourrait traduire en français par « déballage ».

L'unpacking est très utile mais difficile à expliquer et à comprendre tellement il peut servir dans de nombreux cas de figures.

Grâce à l'unpacking on peut par exemple « déballer » les éléments d'une liste dans plusieurs variables :

a, b, c = [1, 2, 3]
print(a)
print(b)
print(c)

Vous devez avoir le même nombre de valeurs de chaque côté du symbole d'égalité :

#norun
a, b, c, d = [1, 2, 3]  # ValueError: not enough values to unpack (expected 4, got 3)
a, b, c = [1, 2]        # ValueError: not enough values to unpack (expected 3, got 2)

On peut palier à ce problème avec l'opérateur splat (*) qu'on accole à une variable qui récupèrera toutes les valeurs permettant à l'itérable à droite du symbole d'égalité d'être correctement « déballé » :

#norun
a, *b, c = (1, 2, 3, 4, 5, 6)
a -> 1
b -> [2, 3, 4, 5]
c -> 6

a, b, *c = (1, 2, 3, 4, 5, 6)
a -> 1
b -> 2
c -> [3, 4, 5, 6]

L'unpacking fonctionne avec tous les itérables :

a, b, c = (1, 2, 3)  # Avec un tuple
print(a)
print(b)
print(c)
a, b, c = [1, 2, 3]  # Avec une liste
print(a)
print(b)
print(c)
a, b, c = {1: "un", 2: "deux", 3: "trois"}  # Avec un dictionnaire
print(a)
print(b)
print(c)
d, o, c, s, t, r, i, n, g = "Docstring"  # Avec une chaîne de caractères
print(d)
print(o)
print(c)
print(s)
print(t)
print(r)
print(i)
print(n)
print(g)

Utilisations de l'unpacking

Dans les fonctions

On peut utiliser l'unpacking dans une fonction pour retourner plusieurs valeurs.

def validate_user(username, password):
    if username == "Thibault" and password == "123":
        return True, 827


user_is_valid, user_id = validate_user(username="Thibault", password="123")
print(user_is_valid)
print(user_id)
Comme pour les exemples ci-dessus, vous devez obligatoirement avoir le même nombre de variables à gauche de l'opérateur = lors de l'appel de la fonction. 

Ainsi, le code suivant vous retournera une erreur : 

def validate_user(username, password):
    if username == "Thibault" and password == "123":
        return True  # On ne retourne qu'une seule valeur


# On a deux variables à gauche du symbole d'égalité lors de l'appel de la fonction
user_is_valid, user_id = validate_user(username="Thibault", password="123")
print(user_is_valid)
print(user_id)

Par convention, on utilise un tiret du bas pour des variables dont nous n'avons pas besoin mais que nous devons insérer dans un script pour prévenir les erreurs.

Si dans l'exemple de cette fonction, l'ID de l'utilisateur ne nous intéresse pas, nous pouvons donc faire :

def validate_user(username, password):
    if username == "Thibault" and password == "123":
        return True, 827


user_is_valid, _ = validate_user(username="Thibault", password="123")
print(user_is_valid)
print(_)

☝️ On indique ici avec le nom _ que cette variable n'aura pas d'utilité dans le reste de notre script.

Dans une boucle

Dans le code ci-dessous, on utilise l'unpacking pour récupérer directement dans deux variable séparées (i et prenom), les éléments contenus dans chaque tuple de notre liste :

for i, prenom in [(1, "John"), (2, "Marie"), (3, "Julie")]:
    print(i, prenom)

Ce principe est le même utilisé lorsque nous parcourons un dictionnaire avec la méthode items :

d = {1: "un", 2: "deux", 3: "trois"}

print(list(d.items()))

for key, value in d.items():
    print(key, value)

Utilisations de l'unpacking dans l'appel d'une fonction

Il est également possible d'utiliser l'unpacking pour passer des arguments lors de l'appel d'une fonction grâce à l'opérateur « splat » (*) :

def rgb2hex(r, g, b):
    return "#{:02x}{:02x}{:02x}".format(r, g, b).upper()


rgb_values = (128, 255, 255)

# Sans l'unpacking
hex_value = rgb2hex(rgb_values[0], rgb_values[1], rgb_values[2])

# Avec l'unpacking
hex_value = rgb2hex(*rgb_values)
print(hex_value)

☝️ Dans cet exemple, la fonction rgb2hex permet de convertir trois nombres entiers allant de 0 à 255 et représentant les valeurs de rouge, de vert et de bleu, en code couleur hexadécimal.

Plutôt que de passer les valeurs une à une avec leur indice, on utilise l'opérateur splat (*) pour « déballer » les valeurs et les donner individuellement en argument à la fonction rgb2hex.

Là encore, il faut faire attention aux nombres de valeurs qui sont passées, le code suivant ne fonctionnera pas car nous envoyons 4 valeurs à la fonction qui n'en accepte que 3 (r, g et b) :

def rgb2hex(r, g, b):
    return "#{:02x}{:02x}{:02x}".format(r, g, b).upper()


rgba_values = (128, 255, 255, 0.5)
hex_value = rgb2hex(*rgba_values)
print(hex_value)