# ❌ Solution limitée : un paramètre par argument
def additionner(a, b):
return a + b
print(additionner(1, 2))
# print(additionner(1, 2, 3)) # TypeError !3
Jusqu’ici, vos fonctions avaient un nombre fixe de paramètres. Python permet de faire mieux : des fonctions qui acceptent un nombre variable d’arguments, positionnels ou nommés. C’est ce que permettent *args et **kwargs — deux constructions très fréquentes dans le code Python réel.
Imaginez une fonction additionner qui doit fonctionner pour 2 nombres, 3, 10… sans qu’on connaisse le nombre à l’avance.
# ❌ Solution limitée : un paramètre par argument
def additionner(a, b):
return a + b
print(additionner(1, 2))
# print(additionner(1, 2, 3)) # TypeError !3
Ou alors on passe une liste, mais l’appel est moins pratique :
def additionner(nombres):
return sum(nombres)
print(additionner([1, 2, 3, 4])) # obligation de mettre la liste10
Idéalement, on voudrait écrire additionner(1, 2, 3, 4) directement. C’est ce que permet *args.
*args — arguments positionnels variablesLe préfixe * dans la signature collecte tous les arguments positionnels supplémentaires dans un tuple.
def additionner(*args):
return sum(args)
print(additionner(1, 2))
print(additionner(1, 2, 3))
print(additionner(1, 2, 3, 4, 5))
print(additionner()) # aucun argument : 03
6
15
0
args est un simple tuple — vous pouvez l’itérer, le mesurer, etc.
def afficher_tout(*args):
print(f"Type : {type(args)}")
print(f"Nombre : {len(args)}")
for i, val in enumerate(args):
print(f" [{i}] {val}")
afficher_tout("Alice", 30, True, [1, 2])Type : <class 'tuple'>
Nombre : 4
[0] Alice
[1] 30
[2] True
[3] [1, 2]
args est une convention, pas une obligation
On pourrait écrire def f(*nombres): et ça fonctionnerait. Mais *args est l’idiome universellement reconnu — tous les développeurs Python savent immédiatement ce que ça signifie. Utilisez args pour rester lisible.
*argsLes paramètres positionnels fixes viennent avant *args.
def rapport(titre, *valeurs):
print(f"=== {titre} ===")
for v in valeurs:
print(f" - {v}")
rapport("Notes", 12, 15, 17, 9)=== Notes ===
- 12
- 15
- 17
- 9
*args est toujours un tupleMême avec un seul argument :
def f(*args):
print(type(args), args)
f(42)
f(42, 43)
f()<class 'tuple'> (42,)
<class 'tuple'> (42, 43)
<class 'tuple'> ()
**kwargs — arguments nommés variablesLe préfixe ** collecte tous les arguments nommés supplémentaires dans un dictionnaire.
def afficher_infos(**kwargs):
for cle, valeur in kwargs.items():
print(f" {cle} = {valeur}")
afficher_infos(nom="Alice", age=30, ville="Paris") nom = Alice
age = 30
ville = Paris
def construire_url(base, **params):
if not params:
return base
query = "&".join(f"{k}={v}" for k, v in params.items())
return f"{base}?{query}"
print(construire_url("http://api.fr/search"))
print(construire_url("http://api.fr/search", q="python", page=2))
print(construire_url("http://api.fr/search", user="alice", limit=50, sort="date"))http://api.fr/search
http://api.fr/search?q=python&page=2
http://api.fr/search?user=alice&limit=50&sort=date
**kwargs est toujours un dictdef f(**kwargs):
print(type(kwargs), kwargs)
f(a=1, b=2)
f()<class 'dict'> {'a': 1, 'b': 2}
<class 'dict'> {}
*args et **kwargsOn peut utiliser les deux dans la même fonction, dans cet ordre strict :
def f(pos_fixe, *args, **kwargs):
...def tout_afficher(titre, *valeurs, **options):
print(f"=== {titre} ===")
print(f" Positionnels : {valeurs}")
print(f" Options : {options}")
tout_afficher("Démo", 1, 2, 3, couleur="rouge", gras=True)=== Démo ===
Positionnels : (1, 2, 3)
Options : {'couleur': 'rouge', 'gras': True}
Dans une signature de fonction, l’ordre est :
*args — positionnels supplémentaires.**kwargs — nommés supplémentaires.def f(a, b=0, *args, option=False, **kwargs):
...* et *** et ** fonctionnent aussi dans l’autre sens : pour déballer une liste ou un dict en arguments lors d’un appel.
*def additionner(a, b, c):
return a + b + c
valeurs = [1, 2, 3]
# Au lieu de additionner(valeurs[0], valeurs[1], valeurs[2])
print(additionner(*valeurs))6
**def presenter(nom, age, ville):
print(f"{nom}, {age} ans, {ville}")
personne = {"nom": "Alice", "age": 30, "ville": "Paris"}
# Au lieu de presenter(nom=personne["nom"], age=personne["age"], ville=personne["ville"])
presenter(**personne)Alice, 30 ans, Paris
*args / *valeurs est élégante
Dans une signature : *args collecte les arguments en tuple. À l’appel : *liste déballe un tuple en arguments.
C’est le même symbole avec deux usages complémentaires — une fois compris, on passe facilement de l’un à l’autre.
C’est très utile pour passer ses propres arguments à une autre fonction :
def loguer_appel(func, *args, **kwargs):
print(f"Appel : {func.__name__}({args}, {kwargs})")
resultat = func(*args, **kwargs) # transmet TOUS les args/kwargs
print(f"Résultat : {resultat}")
return resultat
def additionner(a, b, c):
return a + b + c
loguer_appel(additionner, 1, 2, 3)
loguer_appel(additionner, 1, 2, c=30)Appel : additionner((1, 2, 3), {})
Résultat : 6
Appel : additionner((1, 2), {'c': 30})
Résultat : 33
33
C’est un pattern fondamental pour écrire des décorateurs (chapitre 3 de la Partie 4).
On peut forcer certains paramètres à ne pouvoir être passés que par nom, jamais par position. On les place après *args (ou après un * solo).
*argsdef rapport(titre, *valeurs, separateur=" | "):
print(titre + " : " + separateur.join(str(v) for v in valeurs))
rapport("Notes", 12, 15, 17)
rapport("Notes", 12, 15, 17, separateur=" ; ")
# On NE PEUT PAS passer separateur par position
# rapport("Notes", 12, 15, 17, " ; ") → le " ; " irait dans *valeurs !Notes : 12 | 15 | 17
Notes : 12 ; 15 ; 17
* seulPour rendre des paramètres keyword-only sans utiliser *args, on place un * tout seul :
def creer_utilisateur(nom, *, role="user", actif=True):
return {"nom": nom, "role": role, "actif": actif}
# Par nom : OK
print(creer_utilisateur("Alice", role="admin"))
# Par position : interdit !{'nom': 'Alice', 'role': 'admin', 'actif': True}
def creer_utilisateur(nom, *, role="user", actif=True):
return {"nom": nom, "role": role, "actif": actif}
creer_utilisateur("Alice", "admin") # role passé par position → erreur--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[16], line 4 1 def creer_utilisateur(nom, *, role="user", actif=True): 2 return {"nom": nom, "role": role, "actif": actif} 3 ----> 4 creer_utilisateur("Alice", "admin") # role passé par position → erreur TypeError: creer_utilisateur() takes 1 positional argument but 2 were given
Pour les paramètres booléens ou optionnels qui risquent d’être ambigus par position :
# ❌ Peu lisible
creer_utilisateur("Alice", "admin", True)
# ✅ Explicite
creer_utilisateur("Alice", role="admin", actif=True)C’est une bonne pratique pour des API professionnelles.
** (Python 3.5+)Un usage très pratique : fusionner plusieurs dicts en un.
defauts = {"theme": "clair", "lang": "fr", "taille": 14}
user = {"theme": "sombre", "taille": 16}
# Fusion : user écrase defauts
config = {**defauts, **user}
print(config){'theme': 'sombre', 'lang': 'fr', 'taille': 16}
|
Python 3.9+ propose une syntaxe encore plus simple :
defauts = {"theme": "clair", "lang": "fr"}
user = {"theme": "sombre"}
config = defauts | user
print(config){'theme': 'sombre', 'lang': 'fr'}
Les deux formes coexistent.
Voici une signature complète, dans l’ordre canonique :
def fonction(
a, # positionnel obligatoire
b=0, # positionnel avec défaut
*args, # positionnels supplémentaires → tuple
kw1, # keyword-only obligatoire
kw2="def", # keyword-only avec défaut
**kwargs # nommés supplémentaires → dict
):
...Que vaut args dans l’appel f(1, 2, 3) avec def f(*args): ?
1, 2, 3[1, 2, 3](1, 2, 3){1: 2, 3: ...}c) (1, 2, 3) — *args produit toujours un tuple, même avec un seul élément ou aucun.
def f(*args):
print(type(args), args)
f(1, 2, 3)<class 'tuple'> (1, 2, 3)
Que s’affiche-t-il ?
def f(**kwargs):
print(type(kwargs))
f(a=1, b=2)<class 'list'><class 'tuple'><class 'dict'>c) <class 'dict'> — **kwargs collecte toujours en dictionnaire.
def f(**kwargs):
print(type(kwargs))
f(a=1, b=2)<class 'dict'>
Quel ordre est correct ?
def f(*args, a, **kwargs):def f(a, **kwargs, *args):def f(a, *args, **kwargs):def f(**kwargs, *args, a):c) def f(a, *args, **kwargs): — l’ordre canonique est : positionnels → *args → keyword-only → **kwargs. L’option a) est valide aussi (elle fait de a un keyword-only) mais c) est la forme standard.
Que fait f(*[1, 2, 3]) avec def f(a, b, c): ?
[1, 2, 3] comme seul argumentf(1, 2, 3) en déballant la liste[1, 2, 3]b) — le * à l’appel déballe l’itérable en arguments positionnels. Équivalent à f(1, 2, 3).
def f(a, b, c):
return a + b + c
print(f(*[1, 2, 3]))6
Que s’affiche-t-il ?
d = {"a": 1, "b": 2}
def f(a, b):
return a * b
print(f(**d))212"ab"a) 2 — **d déballe le dict en arguments nommés : équivalent à f(a=1, b=2), soit 1 * 2 = 2.
d = {"a": 1, "b": 2}
def f(a, b):
return a * b
print(f(**d))2
Que s’affiche-t-il ?
def f(nom, *, politesse="Bonjour"):
print(politesse, nom)
f("Alice", "Salut")Salut AliceBonjour AliceTypeErrorBonjour Alice Salutc) Une TypeError — le * seul rend politesse keyword-only. On ne peut pas le passer par position. L’appel correct serait f("Alice", politesse="Salut").
def f(nom, *, politesse="Bonjour"):
print(politesse, nom)
f("Alice", "Salut")--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[23], line 4 1 def f(nom, *, politesse="Bonjour"): 2 print(politesse, nom) 3 ----> 4 f("Alice", "Salut") TypeError: f() takes 1 positional argument but 2 were given
Que vaut config ?
defauts = {"a": 1, "b": 2}
user = {"b": 20, "c": 30}
config = {**defauts, **user}{"a": 1, "b": 2, "c": 30}{"a": 1, "b": 20, "c": 30}{"a": 1, "b": [2, 20], "c": 30}b) {"a": 1, "b": 20, "c": 30} — les clés du second dict écrasent celles du premier en cas de conflit. Ici b passe de 2 à 20.
defauts = {"a": 1, "b": 2}
user = {"b": 20, "c": 30}
print({**defauts, **user}){'a': 1, 'b': 20, 'c': 30}
Écrivez une fonction moyenne(*nombres) qui accepte n’importe quel nombre d’arguments numériques et renvoie leur moyenne. Gérer le cas sans argument.
def moyenne(*nombres):
...
print(moyenne(10, 20, 30))
print(moyenne(5))
print(moyenne())def moyenne(*nombres):
if not nombres:
return 0
return sum(nombres) / len(nombres)
print(moyenne(10, 20, 30))
print(moyenne(5))
print(moyenne())20.0
5.0
0
Écrivez creer_email(destinataire, sujet, **entetes) qui renvoie un dict structuré. Les entetes sont des en-têtes optionnels comme cc, bcc, priorite…
def creer_email(destinataire, sujet, **entetes):
...
email = creer_email(
"alice@example.com",
"Rappel",
cc="bob@example.com",
priorite="haute",
)
print(email)def creer_email(destinataire, sujet, **entetes):
email = {
"to": destinataire,
"subject": sujet,
}
email.update(entetes)
return email
email = creer_email(
"alice@example.com",
"Rappel",
cc="bob@example.com",
priorite="haute",
)
print(email)
# Sans en-têtes optionnels
email_simple = creer_email("charlie@example.com", "Salut")
print(email_simple){'to': 'alice@example.com', 'subject': 'Rappel', 'cc': 'bob@example.com', 'priorite': 'haute'}
{'to': 'charlie@example.com', 'subject': 'Salut'}
Plus Pythonique :
def creer_email(destinataire, sujet, **entetes):
return {"to": destinataire, "subject": sujet, **entetes}
print(creer_email("alice@ex.com", "Salut", cc="bob@ex.com")){'to': 'alice@ex.com', 'subject': 'Salut', 'cc': 'bob@ex.com'}
Écrivez une fonction tracer(fonction, *args, **kwargs) qui :
def tracer(fonction, *args, **kwargs):
...
def addition(a, b):
return a + b
def saluer(nom, politesse="Bonjour"):
return f"{politesse}, {nom}"
tracer(addition, 3, 5)
tracer(saluer, "Alice")
tracer(saluer, "Bob", politesse="Salut")def tracer(fonction, *args, **kwargs):
nom = fonction.__name__
args_str = ", ".join(repr(a) for a in args)
kwargs_str = ", ".join(f"{k}={v!r}" for k, v in kwargs.items())
signature = ", ".join(filter(None, [args_str, kwargs_str]))
print(f">>> {nom}({signature})")
resultat = fonction(*args, **kwargs)
print(f" = {resultat!r}")
return resultat
def addition(a, b):
return a + b
def saluer(nom, politesse="Bonjour"):
return f"{politesse}, {nom}"
tracer(addition, 3, 5)
tracer(saluer, "Alice")
tracer(saluer, "Bob", politesse="Salut")>>> addition(3, 5)
= 8
>>> saluer('Alice')
= 'Bonjour, Alice'
>>> saluer('Bob', politesse='Salut')
= 'Salut, Bob'
'Salut, Bob'
fonction.__name__ donne le nom de la fonction (toute fonction a cet attribut).repr(x) et f"{x!r}" affichent la forme « développeur » (avec guillemets pour les chaînes).fonction(*args, **kwargs) déballe à l’appel — le même symbole fait les deux sens !Écrivez une fonction fusionner_config(*configs) qui reçoit un nombre variable de dicts et les fusionne en un seul — chaque dict écrase les précédents.
def fusionner_config(*configs):
...
defaut = {"theme": "clair", "lang": "fr", "taille": 14}
user = {"theme": "sombre"}
session = {"taille": 18}
print(fusionner_config(defaut, user, session))def fusionner_config(*configs):
resultat = {}
for config in configs:
resultat.update(config)
return resultat
defaut = {"theme": "clair", "lang": "fr", "taille": 14}
user = {"theme": "sombre"}
session = {"taille": 18}
print(fusionner_config(defaut, user, session))
print(fusionner_config()) # dict vide si aucun arg{'theme': 'sombre', 'lang': 'fr', 'taille': 18}
{}
def fusionner_config(*configs):
return {k: v for config in configs for k, v in config.items()}
print(fusionner_config(
{"theme": "clair", "lang": "fr"},
{"theme": "sombre"},
{"taille": 18},
)){'theme': 'sombre', 'lang': 'fr', 'taille': 18}
*args collecte les arguments positionnels en tuple.**kwargs collecte les arguments nommés en dict.(positionnels, *args, keyword-only, **kwargs).*liste déballe la liste en arguments, **dict déballe le dict.* seul rend les paramètres suivants keyword-only.{**d1, **d2} (ou d1 | d2 en Python 3.9+).func(*args, **kwargs) est la base des décorateurs (Partie 4).← Chapitre précédent : Les compréhensions • Chapitre suivant : Les fonctions lambda →