Le sujet autour des arguments Python, souvent résumé par l’étiquette python args, touche à deux choses très différentes: les paramètres d’une fonction et les valeurs passées à un script en ligne de commande. Bien comprendre cette mécanique évite des erreurs banales, des interfaces confuses et des signatures trop permissives. Je vais distinguer les cas utiles, montrer les bons outils et rappeler les pièges qui reviennent le plus souvent.
Les points clés à garder avant de passer au code
- Un paramètre appartient à la définition d’une fonction; un argument est la valeur transmise à l’appel.
-
*argsregroupe les arguments positionnels supplémentaires dans un tuple. -
**kwargsregroupe les arguments nommés supplémentaires dans un dictionnaire. -
sys.argvdonne les arguments d’un script, mais tout arrive sous forme de chaînes. -
argparseest le bon choix dès qu’un script doit être propre, lisible et utilisable par d’autres. - Les marqueurs
/et*servent à rendre une signature plus claire et plus stricte.
Ce que recouvrent vraiment les arguments en Python
Je distingue toujours paramètre et argument. Le paramètre vit dans la définition; l’argument est la valeur transmise au moment de l’appel. Cette nuance semble scolaire, mais elle aide vraiment quand une fonction accepte des valeurs positionnelles, des valeurs nommées, des valeurs par défaut ou des contraintes plus strictes.
En pratique, Python laisse beaucoup de souplesse: on peut appeler une fonction par position, par mot-clé, ou avec un mélange des deux. Depuis quelques versions, la syntaxe permet aussi de verrouiller une partie du contrat grâce à / et *. Je m’en sers souvent quand je veux éviter que l’API devienne ambiguë au fil du temps.
def facturer(client, /, montant, *, devise="EUR"):
return f"{client} doit payer {montant} {devise}"
facturer("Acme", 120)
facturer("Acme", 120, devise="USD")Ici, client est imposé en positionnel, montant reste flexible, et devise doit être donnée par nom. Ce type de signature me plaît parce qu’il protège la fonction sans la rendre lourde à appeler. C’est aussi la base mentale qui rend *args et **kwargs moins mystérieux, ce qu’on voit juste après.

Les signatures de fonction et les différents types d’arguments
Quand une fonction doit accepter un nombre variable d’arguments, *args rassemble les arguments positionnels supplémentaires dans un tuple, et **kwargs rassemble les arguments nommés supplémentaires dans un dictionnaire. Ce n’est pas un détail syntaxique: cela change la façon dont on propage des paramètres d’une fonction à l’autre.
def tracer(*args, **kwargs):
print("positionnels:", args)
print("nommés:", kwargs)
tracer("alpha", 42, debug=True, mode="safe")Dans un code de production, j’utilise surtout cette technique pour écrire des fonctions intermédiaires, des décorateurs ou des adaptateurs qui relaient un appel sans casser la signature de la fonction cible. Le piège, c’est l’excès: à force de tout faire passer en vrac, on perd la lisibilité et les erreurs deviennent plus difficiles à localiser.
Un bon réflexe consiste à réserver *args et **kwargs aux cas où la variabilité est réelle. Si vous connaissez déjà la forme attendue, mieux vaut écrire des paramètres explicites. C’est plus lisible, plus facile à documenter, et les messages d’erreur sont souvent bien meilleurs.
Quand on passe du code métier au code de script, la question devient différente: il faut lire ce que l’utilisateur tape dans le terminal.
Lire les arguments d’un script avec sys.argv
Pour un script, la source brute des arguments se trouve dans sys.argv. L’index 0 contient le nom du script; les éléments suivants sont les valeurs réellement passées. Et surtout, ce sont des chaînes de caractères, pas des entiers, ni des booléens, ni des chemins déjà validés.
import sys
if len(sys.argv) < 3:
raise SystemExit("Usage: python export.py source destination")
source = sys.argv[1]
destination = sys.argv[2]Si je dois lire un nombre, je le convertis explicitement avec int() ou float(). C’est simple, mais fragile dès que le script grandit: il faut gérer les indices manquants, les erreurs de conversion et les options facultatives à la main. Dès qu’un outil doit être utilisé plusieurs fois, je préfère passer à un parseur dédié.
En clair, sys.argv suffit pour un script très court, un prototype ou un utilitaire interne. Au-delà de ce cadre, le code devient vite plus coûteux à maintenir que prévu. C’est là que argparse prend tout son sens.
Construire une interface propre avec argparse
argparse est la solution standard quand je veux un vrai outil en ligne de commande. Le module lit sys.argv, convertit les types, gère l’aide, affiche des messages d’erreur cohérents et me laisse définir des arguments positionnels, des options facultatives et des valeurs par défaut.
import argparse
parser = argparse.ArgumentParser(description="Exporter des données")
parser.add_argument("source")
parser.add_argument("destination")
parser.add_argument("--format", choices=["json", "csv"], default="json")
parser.add_argument("--limit", type=int, default=100)
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args()Le résultat est un objet Namespace simple à manipuler: chaque argument devient un attribut clair, au lieu d’une suite d’indices à retenir. Ce que j’apprécie ici, ce n’est pas seulement le confort. C’est aussi la discipline qu’impose la signature de la CLI: une option mal typée déclenche une erreur claire, l’utilisateur voit l’aide sans que j’écrive tout moi-même, et le code devient plus simple à maintenir.
Pour un script de déploiement, d’automatisation ou d’extraction de données, la différence est nette. Et si l’outil s’étoffe, argparse gère aussi les sous-commandes et les convertisseurs personnalisés sans vous obliger à réinventer le parsing. C’est précisément ce qui le rend plus solide que la lecture manuelle de la ligne de commande.
Les erreurs qui coûtent du temps aux débutants
La plupart des problèmes ne viennent pas de Python lui-même, mais d’hypothèses trop optimistes sur les arguments. Je vois revenir les mêmes fautes, et elles sont faciles à éviter quand on les nomme clairement.
- Confondre paramètre et argument et chercher le bug au mauvais endroit.
-
Oublier que
sys.argvest une liste de chaînes, puis essayer d’utiliser une valeur numérique sans conversion. -
Utiliser des valeurs par défaut mutables comme
[]ou{}quand une fonction reçoit des arguments facultatifs. -
Abuser de
**kwargspour masquer une API floue au lieu d’expliciter les besoins réels. -
Ignorer les signatures alors qu’un simple
help()ouinspect.signature()suffit souvent à clarifier ce que la fonction attend.
def ajouter_tag(tag, tags=None):
if tags is None:
tags = []
tags.append(tag)
return tagsLe cas des valeurs par défaut mutables mérite une attention particulière: si vous écrivez tags=[] dans la signature, la même liste peut survivre entre plusieurs appels. Je préfère le pattern None + initialisation interne, car il reste prévisible et évite des effets de bord difficiles à diagnostiquer. C’est le genre de détail qui paraît anodin jusqu’au jour où un script ajoute des éléments fantômes.
Quand ces bases sont claires, il reste à choisir la bonne stratégie selon le type de code que vous écrivez.
Le bon choix selon le contexte de votre code
Je ramène presque toujours la décision à une question simple: est-ce que j’écris une fonction interne, une petite commande, ou une interface qui doit durer ? La réponse change l’outillage, mais pas l’objectif: rester explicite sans rendre le code lourd.
| Contexte | Approche que je recommande | Pourquoi |
|---|---|---|
| Fonction métier simple | Arguments explicites | Lecture rapide, erreurs visibles, contrat clair |
| Fonction relais, décorateur, adaptateur |
*args et **kwargs
|
Propagation souple vers une autre fonction |
| Script ponctuel | sys.argv |
Peu de code, suffisant pour un usage interne |
| Outil CLI utilisé par une équipe | argparse |
Aide intégrée, validation, types, messages propres |
Dans mon expérience, la règle qui tient le mieux est celle-ci: plus le code est exposé à d’autres personnes, plus les arguments doivent être explicites et validés. Plus le code sert de pont générique, plus *args et **kwargs ont leur place, mais seulement si cette souplesse apporte un vrai bénéfice. C’est cette lecture pragmatique qui évite les APIs fragiles et les scripts difficiles à reprendre.