Python *args & kwargs - Maîtrisez la flexibilité des fonctions

Léon Weiss .

28 février 2026

Code Python montrant l'utilisation de `*args` pour une fonction, illustrant la flexibilité des `python args kwargs`.

La mécanique des paramètres variables en Python sert à écrire des fonctions plus souples sans perdre en lisibilité. Je vais aller droit au but : comprendre la logique derrière python args kwargs, savoir quand les utiliser, comment les combiner avec les marqueurs `/` et `*`, et éviter les erreurs qui rendent une API pénible à maintenir.

L’essentiel à retenir sur les arguments variables en Python

  • *args collecte les arguments positionnels supplémentaires dans un tuple.
  • **kwargs collecte les arguments nommés supplémentaires dans un dictionnaire.
  • Les noms args et kwargs sont une convention, pas une obligation.
  • / impose des paramètres positionnels seulement, * impose des paramètres nommés seulement.
  • Ces outils sont très utiles pour les wrappers, décorateurs, API extensibles et fonctions de passage.
  • Le piège principal est de tout accepter sans filtrer, ce qui masque les fautes de frappe et brouille la signature.

Ce que recouvrent les arguments variables

En Python, une fonction peut accepter un nombre fixe de paramètres, ou au contraire absorber ce qui dépasse grâce à des paramètres variables. *args récupère les arguments positionnels en trop, tandis que **kwargs récupère les arguments nommés en trop. Les deux mécanismes répondent à des besoins différents, même si on les mélange souvent dans le même exemple.

Je fais toujours le rappel suivant quand j’explique ce sujet à une équipe backend : un paramètre est défini dans la fonction, un argument est la valeur passée à l’appel. Cette distinction évite déjà une bonne partie des confusions. Les paramètres variables ne servent pas à “mettre n’importe quoi” dans une fonction ; ils servent à ouvrir un point d’extension propre, contrôlé et documentable.

Concrètement, une fonction comme celle-ci accepte un socle fixe, puis s’adapte au reste :

def enregistrer_evenement(source, *tags, **meta):
    print("source:", source)
    print("tags:", tags)
    print("meta:", meta)

enregistrer_evenement("api", "auth", "login", user_id=42, ip="10.0.0.8")

Ici, source reste obligatoire et clair, tags capture les valeurs positionnelles supplémentaires, et meta garde les options nommées. Quand cette base est bien comprise, la question suivante devient naturelle : comment écrire des signatures lisibles qui ne deviennent pas ambiguës ?

Comment les déclarer et les transmettre

Le plus important, c’est de retenir que les noms args et kwargs sont conventionnels. Je peux très bien écrire *values et **options si cela rend le code plus parlant. Ce qui compte, ce sont les opérateurs * et **, pas le nom choisi.

À l’appel, le mécanisme inverse s’applique aussi. Le dépaquetage permet de réutiliser une séquence ou un dictionnaire existants :

def tracer(*args, **kwargs):
    print(args)
    print(kwargs)

pos = ("GET", "/health")
opts = {"timeout": 2, "retries": 1}

tracer(*pos, **opts)

Cette forme est très utile dans les wrappers, les décorateurs ou les fonctions intermédiaires, parce qu’elle évite de réécrire la signature complète à chaque couche. Mais elle n’est propre que si on garde une règle simple : collecter, puis relayer sans déformer. Si on commence à transformer les données au milieu, il faut le faire explicitement.

Je conseille aussi de penser aux valeurs par défaut avant d’ajouter du variable partout. Très souvent, une fonction a besoin de trois ou quatre paramètres stables, puis d’un espace d’extension. C’est exactement là que *args et **kwargs deviennent utiles plutôt qu’envahissants.

Comment les combiner avec / et *

La vraie lisibilité arrive quand on combine les paramètres variables avec les marqueurs modernes de signature. Python permet de séparer clairement les paramètres positionnels seulement, les paramètres libres, et les paramètres obligatoirement nommés. C’est un détail de syntaxe, mais en pratique il change beaucoup la qualité d’une API.

Forme Ce qu’elle accepte Quand je l’utilise
param Positionnel ou nommé Cas standard, simple à lire
param / Positionnel seulement Quand je veux stabiliser le contrat ou éviter une collision de nom
*, option Nommé seulement Quand je veux forcer la clarté des options
*args Tuple d’arguments positionnels supplémentaires Pass-through, agrégation, décorateurs
**kwargs Dictionnaire d’arguments nommés supplémentaires Configuration extensible, forwarding, options facultatives

Le cas le plus sous-estimé, à mon avis, est la collision entre un nom de paramètre et une clé passée dans **kwargs. Si j’écris def foo(name, **kwargs), je ne peux pas ensuite espérer récupérer une autre valeur sous la clé name sans ambiguïté. Le marqueur / peut justement régler ce problème en imposant un paramètre positionnel seulement :

def foo(name, /, **kwargs):
    return name, kwargs

print(foo("alice", name="bob"))

Ce genre de choix paraît technique, mais il protège l’API dès qu’elle grandit. Et plus la fonction est utilisée par d’autres développeurs, plus cette protection devient concrète.

Les cas d’usage qui valent vraiment le coup

Je réserve les arguments variables à des scénarios où ils apportent une vraie valeur, pas à des fonctions qui veulent juste “tout accepter”. Les usages les plus solides sont souvent les mêmes dans les projets web, backend ou d’automatisation.

  • Les décorateurs : ils doivent souvent transmettre exactement les mêmes arguments que la fonction enveloppée.
  • Les wrappers d’API : ils ajoutent du logging, du retry ou de la validation sans casser la signature cible.
  • Les fonctions de configuration : elles acceptent quelques paramètres connus et laissent passer des options extensibles.
  • Les adaptateurs : ils font le pont entre deux bibliothèques qui n’ont pas la même forme de signature.

Un décorateur simple illustre bien le principe :

def log_calls(func):
    def wrapper(*args, **kwargs):
        print(f"appel de {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

Le point important ici n’est pas seulement la syntaxe, c’est l’intention : je ne réinterprète pas les arguments, je les relaye. Dans un contexte backend, c’est exactement ce qu’on veut quand on ajoute de l’observabilité ou du contrôle transversal sans toucher au comportement métier. Cette approche reste propre tant qu’on ne la transforme pas en fourre-tout généraliste.

Dans une API publique, je trouve aussi utile de limiter les paramètres libres à une couche précise. Cela évite que les options techniques se mélangent aux entrées métier. Autrement dit, **kwargs est excellent comme point d’extension, beaucoup moins comme cache-misère de conception.

Les erreurs que je vois le plus souvent

La première erreur, c’est de confondre flexibilité et flou. Une fonction qui prend *args et **kwargs sans expliquer ce qu’elle attend devient vite difficile à utiliser, surtout dans une équipe. Le code “marche”, mais l’intention disparaît.

La deuxième erreur consiste à tout avaler sans vérifier les clés. Si j’absorbe des options via **kwargs, je dois décider quoi faire des valeurs reconnues et quoi faire du reste. Dans beaucoup de cas, je préfère une validation stricte :

def construire_requete(**kwargs):
    timeout = kwargs.pop("timeout", 5)
    retries = kwargs.pop("retries", 3)

    if kwargs:
        raise TypeError(f"Options inconnues: {', '.join(kwargs)}")

    return {"timeout": timeout, "retries": retries}

Cette petite discipline change tout. Sans elle, une faute de frappe comme tiemout passe silencieusement, et on perd du temps à traquer un bug qui n’a rien de mystérieux. C’est l’un des pièges les plus coûteux des signatures trop permissives.

La troisième erreur, plus subtile, est de masquer une structure claire derrière des paramètres variables alors qu’une liste explicite serait meilleure. Si une fonction attend toujours trois ou quatre valeurs, je n’ai aucune raison de la déguiser en collecteur universel. La simplicité de la signature reste un avantage de maintenance, pas un détail cosmétique.

Enfin, il faut se méfier des noms qui prêtent à confusion. args et kwargs sont des conventions utiles, mais on peut choisir des noms plus expressifs quand le contexte l’exige. Je le fais souvent dans du code orienté produit, parce que la lecture doit rester immédiate pour la personne qui n’a pas écrit la fonction.

Bien typer et inspecter les signatures

Quand on écrit du code moderne, surtout dans un projet web ou une bibliothèque interne, le typage change vraiment la donne. Les décorateurs et wrappers qui manipulent des paramètres variables gagnent beaucoup à être typés proprement, sinon on perd l’aide de l’éditeur et une partie de la validation statique.

Pour préserver la signature d’une fonction dans un décorateur, j’utilise aujourd’hui ParamSpec. C’est le bon outil pour capturer à la fois les paramètres positionnels et nommés d’une fonction sans les réduire à un simple Any :

from typing import Callable, ParamSpec, TypeVar

P = ParamSpec("P")
R = TypeVar("R")

def tracer(func: Callable[P, R]) -> Callable[P, R]:
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        print(f"appel: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

Ce point est plus important qu’il n’en a l’air. Sans ce type de signature, les outils statiques voient souvent une boîte noire, et les erreurs remontent plus tard, parfois trop tard. La documentation officielle de Python insiste d’ailleurs sur ce lien entre P.args, P.kwargs et la signature capturée.

Quand je veux comprendre une fonction existante, j’utilise aussi l’introspection. Le module inspect permet de lire la signature et de distinguer les paramètres positionnels, nommés, variables ou obligatoires. C’est très pratique pour documenter une API interne, générer un outil ou vérifier ce qu’un wrapper transmet réellement :

from inspect import signature

def exemple(a, /, b, *args, c, **kwargs):
    pass

sig = signature(exemple)
for param in sig.parameters.values():
    print(param.name, param.kind)

Je trouve cette approche particulièrement utile quand je reprends une base de code ancienne. Elle permet de vérifier les conventions réelles de la fonction, pas seulement ce que son nom laisse supposer. Et c’est souvent là qu’on voit si une signature a été bien pensée ou simplement accumulée au fil des besoins.

Les repères qui gardent une fonction lisible quand elle grandit

Si je devais résumer ma pratique, je dirais ceci : j’utilise les paramètres variables pour préserver une API claire, pas pour l’évider. Une fonction réussie garde un noyau explicite, puis ouvre un espace mesuré pour les extensions. C’est ce qui rend le code plus stable, plus testable et plus simple à relire six mois plus tard.

Je garde aussi trois réflexes très concrets. D’abord, je n’accepte pas plus que nécessaire. Ensuite, je nomme les paramètres pour refléter leur rôle réel. Enfin, je valide ce qui entre dans **kwargs au lieu de compter sur la bonne volonté des appelants. Ces trois points suffisent souvent à transformer une signature brouillonne en interface solide.

  • Si la fonction est publique, je privilégie une signature explicite.
  • Si elle sert de relais, je laisse passer les paramètres sans les réinventer.
  • Si elle doit rester évolutive, j’utilise /, *, *args et **kwargs avec une intention claire.

Au fond, la bonne question n’est pas “est-ce que je peux ajouter *args et **kwargs ?”, mais “est-ce que cette flexibilité améliore vraiment l’interface ?”. Quand la réponse est oui, le code gagne en souplesse sans perdre sa forme. Quand la réponse est non, une signature plus stricte est presque toujours le meilleur choix.

Questions fréquentes

*args collecte les arguments positionnels supplémentaires dans un tuple, tandis que kwargs collecte les arguments nommés supplémentaires dans un dictionnaire. Ils rendent les fonctions plus flexibles pour gérer un nombre variable d'arguments.
*args (pour "arguments") gère les arguments positionnels non-nommés, tandis que kwargs (pour "keyword arguments") gère les arguments nommés (clés-valeurs). Ils sont souvent utilisés ensemble pour une flexibilité maximale.
Oui, les noms "args" et "kwargs" sont une convention, pas une obligation. Vous pouvez utiliser n'importe quel nom valide précédé de * ou (ex: *valeurs, options), tant que cela améliore la lisibilité de votre code.
Le marqueur `/` force les paramètres à être positionnels seulement, évitant les conflits de noms. Le marqueur `*` force les paramètres suivants à être nommés seulement, améliorant la clarté et la robustesse de l'API.
Évitez de tout accepter sans validation. Validez les clés dans kwargs et n'utilisez ces mécanismes que lorsque la flexibilité est réellement nécessaire. Privilégiez des signatures explicites pour les fonctions publiques et les paramètres stables.
Évaluer l'article

Moyenne: 0.0 / 5 · 0 évaluations

Tags

python args kwargs python *args kwargs explication python *args kwargs exemple python paramètres variables
Autor Léon Weiss
Léon Weiss
Je m'appelle Léon Weiss et je suis développeur web avec trois ans d'expérience dans le domaine. Mon parcours a commencé par une curiosité insatiable pour la programmation et la création de solutions numériques. Je me suis rapidement spécialisé dans des technologies telles que JavaScript, le développement backend, et les bases de données NoSQL, tout en accordant une attention particulière à la sécurité des applications. J'apprécie particulièrement expliquer des concepts complexes de manière accessible, ce qui me permet d'aider les lecteurs à surmonter les défis qu'ils rencontrent dans leur apprentissage. Je m'efforce de fournir des informations utiles, précises et à jour, en vérifiant mes sources et en suivant les tendances du secteur. Mon objectif est de rendre le développement web plus compréhensible et d'accompagner chacun dans son parcours, qu'il soit débutant ou développeur confirmé.
Commentaires (0)
Ajouter un commentaire