Le dépaquetage désigne l'action d'extraire les éléments d'une collection, comme une list, un tuple ou un dict, par itération.
Les valeurs dépaquetées peuvent ensuite être affectées à des variables dans la même instruction.
Un exemple très courant de ce comportement est for item in list, où item prend tour à tour la valeur de chaque élément de la list tout au long de l'itération.
L'affectation multiple permet d'affecter plusieurs variables à des valeurs dépaquetées dans une seule instruction.
Cela permet d'obtenir un code plus concis et plus lisible, et se fait en séparant par une virgule les variables à affecter, comme dans first, second, third = (1,2,3) ou for index, item in enumerate(iterable).
Les opérateurs spéciaux * et ** sont souvent utilisés dans des contextes de dépaquetage.
* permet de combiner plusieurs lists/tuples en une seule list/tuple, en dépaquetant chacune dans une nouvelle list/tuple commune.
** permet de combiner plusieurs dictionnaires en un seul dictionnaire, en dépaquetant chacun dans un nouveau dict commun.
Quand l'opérateur * est utilisé sans collection, il empaquette un certain nombre de valeurs dans une list.
C'est souvent utilisé en affectation multiple pour regrouper dans une seule variable tous les éléments « restants » qui n'ont pas d'affectation individuelle.
En Python, il est courant d'exploiter aussi ce comportement de dépaquetage/empaquetage lorsqu'on utilise ou que l'on définit des fonctions qui prennent un nombre arbitraire d'arguments positionnels ou nommés.
On voit souvent ces paramètres « spéciaux » définis sous la forme def some_function(*args, **kwargs), et ces arguments « spéciaux » utilisés sous la forme some_function(*some_tuple, **some_dict).
*<variable_name> et **<variable_name> ne doivent pas être confondus avec * et **. Alors que * et ** servent respectivement à la multiplication et à l'exponentiation, *<variable_name> et **<variable_name> sont des opérateurs d'empaquetage et de dépaquetage.
Dans une affectation multiple, le nombre de variables à gauche de l'opérateur d'affectation (=) doit correspondre au nombre de valeurs à droite.
Pour séparer les valeurs, on utilise une virgule , :
>>> a, b = 1, 2
>>> a
1
Si l'affectation multiple reçoit un nombre incorrect de variables pour les valeurs données, une ValueError est levée :
>>> x, y, z = 1, 2
ValueError: too many values to unpack (expected 3, got 2)
L'affectation multiple n'est pas limitée à un seul type de données :
>>> x, y, z = 1, "Hello", True
>>> x
1
>>> y
'Hello'
>>> z
True
L'affectation multiple permet d'échanger des éléments dans des lists.
Cette pratique est assez courante dans les algorithmes de tri.
Par exemple :
>>> numbers = [1, 2]
>>> numbers[0], numbers[1] = numbers[1], numbers[0]
>>> numbers
[2, 1]
Comme les tuples sont immuables, on ne peut pas échanger les éléments d'un tuple.
Les exemples ci-dessous utilisent des lists, mais les mêmes concepts s'appliquent aux tuples.
En Python, il est possible de dépaqueter les éléments d'une list/tuple/dict dans des variables distinctes.
Comme les valeurs apparaissent dans les lists/tuples dans un ordre précis, elles sont dépaquetées dans les variables dans ce même ordre :
>>> fruits = ["apple", "banana", "cherry"]
>>> x, y, z = fruits
>>> x
"apple"
Si certaines valeurs ne sont pas nécessaires, on peut utiliser _ pour les signaler :
>>> fruits = ["apple", "banana", "cherry"]
>>> _, _, z = fruits
>>> z
"cherry"
Dépaqueter et affecter des valeurs provenant d'une list/tuple située à l'intérieur d'une list ou d'un tuple (aussi appelés listes ou tuples imbriqués) fonctionne comme un dépaquetage superficiel, mais demande souvent des précisions pour clarifier le contexte ou la position de la valeur :
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [c, d]] = fruits_vegetables
>>> a
"apple"
>>> d
"potato"
On peut aussi dépaqueter en profondeur seulement une partie d'une list/tuple imbriquée :
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [a, [c, d]] = fruits_vegetables
>>> a
["apple", "banana"]
>>> c
"carrot"
Si le dépaquetage comporte des variables mal placées ou un nombre incorrect de valeurs, on obtient une ValueError :
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [d]] = fruits_vegetables
ValueError: too many values to unpack (expected 1)
list/tuple avec *
Quand on dépaquette une list/tuple, on peut utiliser l'opérateur * pour récupérer les valeurs « restantes ».
C'est plus clair que de découper la list/tuple (ce qui, dans certaines situations, est moins lisible).
Par exemple, on peut extraire le premier élément et empaqueter les valeurs restantes dans une nouvelle list sans le premier élément :
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *last = fruits
>>> x
"apple"
>>> last
["banana", "cherry", "orange", "kiwi", "melon", "mango"]
On peut aussi extraire les valeurs au début et à la fin de la list tout en regroupant toutes celles du milieu :
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *middle, y, z = fruits
>>> y
"melon"
>>> middle
["banana", "cherry", "orange", "kiwi"]
On peut aussi utiliser * dans un dépaquetage profond :
>>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>>> [[a, *rest], b] = fruits_vegetables
>>> a
"apple"
>>> rest
["banana", "melon"]
Le dépaquetage d'un dictionnaire est un peu différent du dépaquetage d'une list/tuple.
Par défaut, l'itération sur les dictionnaires porte sur les clés.
Donc, quand on dépaquette un dict, on ne peut dépaqueter que les clés, et pas les valeurs :
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory
>>> x
"apple"
Si on veut dépaqueter les valeurs, on peut utiliser la méthode <dict>.values() :
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.values()
>>> x
6
Si on a besoin à la fois des clés et des valeurs, on utilise la méthode <dict>.items().
<dict>.items() génère une vue itérable contenant des paires clé-valeur.
Celles-ci peuvent être dépaquetées dans un tuple :
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.items()
>>> x
("apple", 6)
L'empaquetage permet de regrouper plusieurs valeurs dans une seule list que l'on affecte à une variable.
C'est utile quand on veut dépaqueter des valeurs, y apporter des modifications, puis empaqueter les résultats dans une variable.
Cela permet aussi de fusionner deux lists/tuples/dicts ou davantage.
list/tuple avec *
On peut empaqueter une list/tuple à l'aide de l'opérateur *.
Toutes les valeurs sont alors empaquetées dans une list/tuple.
>>> fruits = ("apple", "banana", "cherry")
>>> more_fruits = ["orange", "kiwi", "melon", "mango"]
# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits
>>> combined_fruits = *fruits, *more_fruits
# If there is no * on to the left of the "=" the result is a tuple
>>> combined_fruits
("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango")
# If the * operator is used on the left side of "=" the result is a list.
# Note the trailing comma.
>>> *combined_fruits_too, = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']
# A list literal can be used instead, but might not be as readable.
>>> [*combined_fruits_too] = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']
Pour plus de détails sur l'utilisation de * et **, consulte PEP 3132 et PEP 448.
**
L'empaquetage d'un dictionnaire se fait à l'aide de l'opérateur **.
Celui-ci empaquette toutes les paires clé-valeur d'un dictionnaire dans un autre dictionnaire, ou combine deux dictionnaires entre eux.
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> more_fruits_inventory = {"orange": 4, "kiwi": 1, "melon": 2, "mango": 3}
# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined.
>>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory}
# then the pairs are packed into combined_fruits_inventory
>>> combined_fruits_inventory
{"apple": 6, "banana": 2, "cherry": 3, "orange": 4, "kiwi": 1, "melon": 2, "mango": 3}
* et ** avec les fonctionsQuand on crée une fonction qui accepte un nombre arbitraire d'arguments, on peut utiliser *args ou **kwargs dans la définition de la fonction.
*args sert à empaqueter un nombre arbitraire d'arguments positionnels (non nommés) et
**kwargs sert à empaqueter un nombre arbitraire d'arguments nommés.
Utilisation de *args :
# This function is defined to take any number of positional arguments
>>> def my_function(*args):
... print(args)
# Arguments given to the function are packed into a tuple
>>> my_function(1, 2, 3)
(1, 2, 3)
>>> my_function("Hello")
("Hello")
>>> my_function(1, 2, 3, "Hello", "Mars")
(1, 2, 3, "Hello", "Mars")
Utilisation de **kwargs :
# This function is defined to take any number of keyword arguments
>>> def my_function(**kwargs):
... print(kwargs)
# Arguments given to the function are packed into a dictionary
>>> my_function(a=1, b=2, c=3)
{"a": 1, "b": 2, "c": 3}
*args et **kwargs peuvent aussi être utilisés en combinaison l'un avec l'autre :
>>> def my_function(*args, **kwargs):
... print(sum(args))
... for key, value in kwargs.items():
... print(str(key) + " = " + str(value))
>>> my_function(1, 2, 3, a=1, b=2, c=3)
6
a = 1
b = 2
c = 3
On peut aussi écrire des paramètres avant *args pour définir des arguments positionnels précis.
Les arguments nommés individuels doivent alors apparaître avant **kwargs.
Les arguments doivent être structurés comme ceci :
def my_function(<positional_args>, *args, <key-word_args>, **kwargs)
Si tu ne respectes pas cet ordre, tu obtiendras une erreur.
>>> def my_function(a, b, *args):
... print(a)
... print(b)
... print(args)
>>> my_function(1, 2, 3, 4, 5)
1
2
(3, 4, 5)
Écrire les arguments dans un ordre incorrect provoque une erreur :
>>> def my_function(*args, a, b):
... print(args)
>>>my_function(1, 2, 3, 4, 5)
Traceback (most recent call last):
File "c:\something.py", line 3, in <module>
my_function(1, 2, 3, 4, 5)
TypeError: my_function() missing 2 required keyword-only arguments: 'a' and 'b'
On peut utiliser * pour dépaqueter une list/tuple d'arguments dans un appel de fonction.
C'est très utile pour les fonctions qui n'acceptent pas d'iterable :
>>> def my_function(a, b, c):
... print(c)
... print(b)
... print(a)
numbers = [1, 2, 3]
>>> my_function(*numbers)
3
2
1
Utiliser le dépaquetage avec * et la fonction native zip() est un autre cas d'usage courant.
La fonction zip() prend plusieurs itérables et renvoie une list de tuples où les valeurs de chaque iterable sont regroupées :
>>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True])
>>> a, *rest = zip(*values)
>>> rest
[('y', 2, False), ('z', 3, True)]