Cours de Python / Collecter et déballer les arguments avec *args et **kwargs
Dans une définition de fonction, *args recueille les arguments positionnels supplémentaires dans un tuple et **kwargs recueille les arguments nommés supplémentaires dans un dictionnaire. Lors d’un appel, * et ** font l’opération inverse : ils déballent une collection pour transmettre des arguments séparés.
Distinguer collecte et déballage
args et kwargs sont des noms conventionnels ; ce sont les étoiles et leur position qui définissent le rôle. L’exemple emploie ainsi *amounts et **fields. Une fonction à nombre variable d’arguments, dite variadique, est utile lorsque cette souplesse correspond au problème ; elle ne remplace pas systématiquement des paramètres explicites dont le sens est connu.
| Emplacement | Forme | Effet |
|---|---|---|
| Définition | def f(*args) |
Collecte les arguments positionnels supplémentaires dans un tuple |
| Définition | def f(**kwargs) |
Collecte les arguments nommés supplémentaires dans un dictionnaire |
| Appel | f(*values) |
Transmet les éléments d’un itérable comme arguments positionnels |
| Appel | f(**options) |
Transmet les entrées d’un mapping à clés chaînes comme arguments nommés |
Les paramètres placés après *amounts, comme fee ci-dessous, sont nommés uniquement. Les règles des arguments positionnels, nommés et par défaut continuent donc de s’appliquer. La documentation Python des arguments arbitraires et de leur déballage présente les deux directions.
Calculer un total et décrire des options
Python 3 et sa bibliothèque standard suffisent. Enregistrez le programme complet ci-dessous dans varargs.py, en UTF-8, dans un dossier d’exercice. Ouvrez le terminal dans ce dossier avec l’environnement virtuel du projet activé, puis lancez python varargs.py. Sur macOS ou Ubuntu hors environnement virtuel, utilisez python3 varargs.py si votre interpréteur porte ce nom.
def total(*amounts, fee=0):
return sum(amounts) + fee
def describe(**fields):
return ", ".join(f"{key}={value}" for key, value in sorted(fields.items()))
values = [3500, 8000, 2500]
print(total(*values, fee=500))
options = {"category": "food", "currency": "KRW"}
print(describe(**options))
Le résultat attendu est :
14500
category=food, currency=KRW
*values fournit trois nombres séparés ; amounts les reçoit sous forme de tuple. Leur somme vaut 14000 et les frais ajoutent 500, d’où 14500. fee reste une option nommée et ne fait pas partie de ce tuple. Aucun montant transmis donnerait une somme initiale nulle ; les frais resteraient néanmoins ajoutés s’ils étaient fournis.
**options fournit category et currency comme arguments nommés ; la fonction describe les collecte dans fields. Elle trie les entrées par clé, construit les morceaux clé=valeur, puis les réunit avec une virgule et un espace. food et KRW restent des données de l’exercice ; describe ne valide pas leur sens métier.
Repérer les doublons et les mauvais déballages
Les clés d’un mapping déballé avec ** doivent être des chaînes. Une fonction avec des paramètres fixes n’accepte que les noms prévus ; une fonction possédant **fields peut recueillir les autres noms, mais cela ne prouve pas qu’ils soient valides pour l’application. Dans les deux cas, un même argument ne peut pas être fourni deux fois dans le même appel.
Par exemple, describe(category="food", **{"category": "bus"}) lève une TypeError avant l’exécution du corps : les valeurs ne sont pas fusionnées par remplacement. Cette règle diffère de la construction d’un dictionnaire où une valeur ultérieure peut remplacer une clé existante. Indiquer deux fois littéralement le même mot-clé dans l’appel est même rejeté comme une SyntaxError.
total(values) transmet une seule liste, tandis que total(*values) transmet ses trois nombres. Le premier appel ne correspond pas au calcul attendu et l’addition de la liste dans sum provoque ici une TypeError. De même, total(*"123") transmet les chaînes "1", "2" et "3", pas le nombre 123 ; ces chaînes ne sont pas des montants numériques pour sum.
Un wrapper de forme def wrapper(*args, **kwargs) peut recueillir une invocation puis la transmettre avec return func(*args, **kwargs). Oublier les étoiles et écrire func(args, kwargs) transmet seulement deux objets, le tuple et le dictionnaire. Il faut aussi éviter d’ajouter explicitement un mot-clé déjà présent dans kwargs. Le déballage ne résout pas ces collisions.
Tester la souplesse sans perdre le contrat
- Ajoutez seulement zéro à la fin de values, puis relancez le fichier : le total reste 14500. Retirez ensuite ce zéro.
- Remplacez seulement
total(*values, fee=500)partotal(*values). Le défaut fee=0 s’applique et le résultat devient 14000. Rétablissez les frais à 500. - Ajoutez temporairement à la fin du fichier l’appel dupliqué
describe(category="food", **{"category": "bus"}). Les deux lignes normales s’affichent, puis uneTypeErrorsignale le doublon. Supprimez cet essai et retrouvez exactement les deux lignes de référence.
Une interface acceptant des mots-clés arbitraires peut aussi accepter une faute de frappe sans erreur immédiate : currncy serait simplement une autre clé dans describe. Vérifiez les options autorisées et les champs obligatoires avant de transmettre des données à un autre composant, à une API ou à une requête SQL. Pour une collection ayant un sens unique, un paramètre explicite de liste peut communiquer le contrat plus clairement qu’une suite d’arguments arbitraires.




