Le dépaquetage désigne l'action d'extraire les éléments d'une collection, comme une list, un tuple ou un dict, en utilisant l'itération.
Les valeurs dépaquetées peuvent ensuite être affectées à des variables dans la même instruction, ce que l'on appelle communément l'affectation multiple.
Les opérateurs spéciaux * et ** sont souvent utilisés dans des contextes de dépaquetage et avec l'affectation multiple.
Il ne faut pas confondre *<variable_name> et **<variable_name> avec * et **. Alors que * et ** servent respectivement à la multiplication et à l'exponentiation, *<variable_name> et **<variable_name> servent d'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, 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 sera 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, tu ne peux pas échanger d'éléments dans 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, d'un tuple ou d'un dictionary 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 le même ordre :
>>> fruits = ["apple", "banana", "cherry"]
>>> x, y, z = fruits
>>> x
"apple"
Si certaines valeurs ne sont pas nécessaires, tu peux utiliser _ pour les signaler :
>>> fruits = ["apple", "banana", "cherry"]
>>> _, _, z = fruits
>>> z
"cherry"
Dépaqueter et affecter des valeurs provenant d'une list/d'un tuple imbriqué dans une list ou un tuple (aussi appelés listes/tuples imbriqués) fonctionne de la même manière qu'un dépaquetage superficiel, mais nécessite souvent des qualificatifs pour clarifier le contexte ou la position :
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [c, d]] = fruits_vegetables
>>> a
"apple"
>>> d
"potato"
Tu peux aussi dépaqueter en profondeur seulement une partie d'une list/d'un tuple imbriqué :
>>> 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, tu obtiendras une ValueError :
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [d]] = fruits_vegetables
ValueError: too many values to unpack (expected 1)
*
Quand tu dépaquettes une list/un tuple, tu peux utiliser l'opérateur * pour capturer les valeurs « restantes ».
C'est plus clair qu'un découpage de la list/du tuple (qui, dans certaines situations, est moins lisible).
Par exemple, on peut extraire le premier élément, puis placer 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 du début et de la fin de la list tout en regroupant toutes les valeurs du milieu :
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *middle, y, z = fruits
>>> y
"melon"
>>> middle
["banana", "cherry", "orange", "kiwi"]
On peut également 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/d'un tuple.
L'itération sur les dictionnaires porte par défaut sur les clés.
Donc, quand tu dépaquettes un dict, tu ne peux 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 tu veux dépaqueter les valeurs, tu peux utiliser la méthode <dict>.values() :
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.values()
>>> x
6
Si tu as besoin à la fois des clés et des valeurs, 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 est la capacité de regrouper plusieurs valeurs dans une seule list affectée à une variable.
C'est utile quand tu veux dépaqueter des valeurs, les modifier, puis empaqueter à nouveau le résultat dans une variable.
Cela permet également d'effectuer des fusions entre 2 lists/tuples/dicts ou plus.
*
L'empaquetage d'une list/d'un tuple se fait avec l'opérateur *.
Cela empaquette toutes les valeurs dans une list/un 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']
Pour en savoir plus sur l'utilisation de * du côté gauche, consulte la PEP 3132.
**
L'empaquetage d'un dictionnaire se fait avec l'opérateur **.
Cela 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 tu crées une fonction qui accepte un nombre arbitraire d'arguments, tu peux utiliser *args ou **kwargs dans la définition de la fonction.
*args sert à empaqueter un nombre arbitraire d'arguments positionnels (non nommés) dans un tuple, et
**kwargs sert à empaqueter un nombre arbitraire d'arguments nommés dans un dictionnaire.
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
Tu peux aussi écrire des paramètres avant *args pour permettre 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 provoquera 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'
Tu peux utiliser * pour dépaqueter une list/un 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 regroupant les valeurs de chaque iterable :
>>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True])
>>> a, *rest = zip(*values)
>>> rest
[('y', 2, False), ('z', 3, True)]
Ton ami Linus est conducteur de locomotive et conduit des trains de marchandises entre les villes. S'il est excellent pour conduire les trains, il l'est beaucoup moins pour la logistique et l'informatique. Il aimerait faire appel à tes talents de programmeur pour organiser les détails des trains et corriger les erreurs dans les données d'itinéraire.
Cet exercice pourrait facilement être résolu en utilisant le découpage, l'indexation et diverses méthodes de dict.
Cependant, nous aimerions que tu t'entraînes à l'empaquetage, au dépaquetage et à l'affectation multiple pour résoudre chacune des tâches ci-dessous.
Ton ami a noté l'identifiant (ID) de chaque wagon, mais il n'est jamais sûr du nombre de wagons que le système devra traiter à un moment donné.
Il serait bien plus simple, pour le reste du programme logistique, que ces données soient regroupées dans une list unifiée.
Implémente une fonction get_list_of_wagons() qui accepte un nombre arbitraire d'identifiants de wagon.
Chaque identifiant sera un entier positif.
La fonction doit ensuite return les identifiants donnés sous la forme d'une seule list.
>>> get_list_of_wagons(1, 7, 12, 3, 14, 8, 5)
[1, 7, 12, 3, 14, 8, 5]
À ce stade, tu commences à te faire une idée des données et de la manière dont elles sont utilisées dans le programme logistique. Le système d'identifiants attribue toujours l'ID 1 à la locomotive, et le reste des wagons du train reçoit un ID choisi au hasard, supérieur à 1.
Ton ami a dû raccorder deux nouveaux wagons au train et a oublié de mettre à jour le système !
Maintenant, les deux premiers wagons de la list du train doivent être déplacés à la fin, sinon tout sera dans le désordre.
Pour compliquer les choses, ton ami vient de découvrir une seconde list qui semble contenir des identifiants de wagons manquants.
Tout ce dont il se souvient, c'est qu'une fois les nouveaux wagons déplacés, les identifiants de cette seconde list doivent être placés juste après la locomotive désignée.
Linus te serait vraiment reconnaissant de corriger ses erreurs et de consolider les données.
Implémente une fonction fix_list_of_wagons() qui prend deux lists contenant des identifiants de wagons.
Elle doit déplacer les deux premiers éléments de la première list à la fin, et insérer les valeurs de la seconde list derrière (du côté droit de) la locomotive (ID 1).
La fonction doit ensuite return une list avec les modifications.
>>> fix_list_of_wagons([2, 5, 1, 7, 4, 12, 6, 3, 13], [3, 17, 6, 15])
[1, 3, 17, 6, 15, 7, 4, 12, 6, 3, 13, 2, 5]
Maintenant que toutes les données des wagons sont correctes, Linus aimerait que tu mettes à jour les informations d'itinéraire du système.
Le long d'un itinéraire de transport, un train peut s'arrêter à quelques gares différentes pour charger et/ou décharger de la marchandise.
Chaque trajet peut avoir un nombre différent de ces points de livraison intermédiaires.
Ton ami aimerait que tu mettes à jour le dict d'itinéraire du système avec les informations de livraison manquantes ou supplémentaires.
Implémente une fonction add_missing_stops() qui accepte un dict d'itinéraire suivi d'un nombre variable d'arguments nommés.
Ces arguments peuvent prendre la forme d'un dict contenant un ou plusieurs arrêts, ou d'un nombre quelconque de paires nommées stop_number=city.
Ta fonction doit alors renvoyer le dict d'itinéraire mis à jour avec une key supplémentaire contenant une list de tous les arrêts ajoutés, dans l'ordre.
>>> add_missing_stops({"from": "New York", "to": "Miami"},
stop_1="Washington, DC", stop_2="Charlotte", stop_3="Atlanta",
stop_4="Jacksonville", stop_5="Orlando")
{"from": "New York", "to": "Miami", "stops": ["Washington, DC", "Charlotte", "Atlanta", "Jacksonville", "Orlando"]}
Linus travaille sur le programme d'itinéraires et a remarqué que certains itinéraires manquent de détails importants.
Les informations d'itinéraire initiales ont été construites sous forme de dict, et ton ami aimerait que tu mettes à jour ce dict avec tout ce qui pourrait manquer.
Chaque itinéraire du système nécessite des détails légèrement différents, donc Linus préférerait vraiment une solution générique.
Implémente une fonction extend_route_information() qui accepte deux dicts.
Le premier dict contient les villes d'origine et de destination entre lesquelles passe l'itinéraire du train.
Le second dict contient d'autres détails d'itinéraire comme la vitesse, la longueur ou la température du train.
La fonction doit renvoyer un dict consolidé avec toutes les informations d'itinéraire.
Le second dict peut contenir des propriétés différentes ou plus nombreuses que celles montrées dans l'exemple.
>>> extend_route_information({"from": "Berlin", "to": "Hamburg"}, {"length": "100", "speed": "50"})
{"from": "Berlin", "to": "Hamburg", "length": "100", "speed": "50"}
Quand Linus a inspecté le dépôt de wagons, il a remarqué que les wagons n'étaient pas stockés dans le bon ordre. En plus d'un identifiant, chaque wagon a une couleur qui correspond au type de marchandise qu'il transporte. Les wagons sont stockés dans le dépôt sous forme de grilles, où chaque colonne de la grille contient des wagons de la même couleur.
Cependant, le système logistique montre que les lists de wagons à stocker dans le dépôt ont leurs lignes regroupées par couleur.
Mais pour que la grille de stockage fonctionne correctement, chaque ligne doit contenir trois couleurs différentes afin que les colonnes s'alignent par couleur.
Ton ami aimerait que tu réorganises les lists du dépôt de wagons, pour que les wagons soient stockés correctement.
Implémente une fonction fix_wagon_depot() qui accepte une list de trois éléments.
Chaque élément de la list est une sous-liste (ou « ligne ») qui contient trois tuples.
Chaque tuple est une paire (<wagon ID>, <wagon color>).
Ta fonction doit renvoyer une list où les trois lists de « lignes » sont réorganisées afin que les wagons soient placés aux bonnes positions.
>>> fix_wagon_depot([
[(2, "red"), (4, "red"), (8, "red")],
[(5, "blue"), (9, "blue"), (13,"blue")],
[(3, "orange"), (7, "orange"), (11, "orange")],
])
[
[(2, "red"), (5, "blue"), (3, "orange")],
[(4, "red"), (9, "blue"), (7, "orange")],
[(8, "red"), (13,"blue"), (11, "orange")]
]
Inscris-toi sur Exercism pour apprendre et maîtriser Python avec 17 concepts146 exercices, et un vrai mentorat humain, le tout gratuitement.