Arguments de fonction — noms, valeurs par défaut et étoiles
Passer des arguments par leur nom, les valeurs par défaut et la règle sur leur ordre, *args et **kwargs, et déballer une liste ou un dictionnaire dans un appel.
Comment voulez-vous apprendre ?
Le problème que nous résolvons
Les fonctions du chapitre précédent ne pouvaient être appelées que d'une seule façon : autant d'arguments qu'il y a de paramètres, dans exactement cet ordre.
En pratique, ce n'est pas suffisant. Prenons une fonction pour une commande :
def order(item, quantity):
return f"{quantity} x {item}"
print(order("pen", 3))
print(order(3, "pen"))3 x pen
pen x 3La deuxième ligne n'a levé aucune erreur — elle a simplement donné une réponse absurde. Python ne vérifie pas les types des arguments : ici, une chaîne et un nombre ont été intervertis, et comme une f-string peut afficher les deux, rien ne l'a arrêté. Python n'a aucun moyen de savoir que l'ordre a été inversé.
Et si presque toutes les commandes portent sur un seul article ? Vous taperiez 1 à chaque fois. Si une fonction a quatre réglages facultatifs ? Vous devriez tous les passer à chaque fois, y compris ceux que vous ne changez pas.
Ce chapitre présente les autres façons de passer des arguments : par nom, avec des valeurs par défaut, et en nombre inconnu.
À la fin de ce chapitre vous pourrez
- Passer des arguments par leur nom, et dire quand c'est préférable
- Donner des valeurs par défaut, et respecter la règle sur leur ordre
- Rendre certains paramètres keyword-only, c'est-à-dire passables uniquement par leur nom
- Accepter un nombre quelconque d'arguments avec
*args - Accepter des arguments de n'importe quel nom avec
**kwargs - Déballer une liste ou un dictionnaire dans un appel
Prérequis : Fonctions — une décision, un nom.
Passer les arguments par leur nom
Au lieu de retenir l'ordre, écrivez le nom :
def order(item, quantity):
return f"{quantity} x {item}"
print(order(quantity=3, item="pen"))3 x penAvec les noms, l'ordre n'a plus d'importance — quantity a été écrit en premier et a tout de même atterri au bon endroit.
Quand le faire ? Quand l'argument ne s'explique pas de lui-même. order("pen", 3) se lit bien. Mais send(True, False) ne dit rien, alors que send(retry=True, verbose=False) le dit. Règle simple : nommez les arguments quand vous passez True/False ou un simple nombre.
Valeurs par défaut
def order(item, quantity=1):
return f"{quantity} x {item}"
print(order("pen"))
print(order("pen", 5))
print(order("pen", quantity=5))1 x pen
5 x pen
5 x penquantity=1 dit « prendre 1 si personne ne précise autrement ». Le cas courant devient court, et le cas rare reste possible.
Une règle doit être respectée : les paramètres avec une valeur par défaut viennent toujours en dernier.
def order(quantity=1, item):
return itemSyntaxError: parameter without a default follows parameter with a defaultLa raison est claire dès qu'on y réfléchit : avec order("pen"), comment Python saurait-il à quel paramètre va "pen" ? Quand les paramètres obligatoires viennent en premier, il n'y a aucune ambiguïté.
Un nombre quelconque d'arguments — *args
def total(*prices):
return sum(prices)
print(total(15, 60))
print(total(15, 60, 850))
print(total())75
925
0L'étoile dans *prices signifie « rassembler ici les arguments positionnels restants ». Pas besoin de décider à l'avance combien il y en aura, et n'en recevoir aucun est permis.
Quelle est la chose rassemblée ?
def show(*things):
print(type(things))
print(things)
show(1, "two")<class 'tuple'>
(1, 'two')Un tuple — le tuple du chapitre quatorze. Le raisonnement est simple : le nombre d'arguments est fixé au moment de l'appel et ne doit pas changer ensuite, donc un conteneur immuable convient.
Le nompricesn'est pas obligatoire. Le nom conventionnel est*args, que vous verrez dans la plupart du code. Mais un nom parlant vaut mieux quand il en existe un —*pricesindique au lecteur ce qui arrive,*argsnon.
Des arguments de n'importe quel nom — **kwargs
def describe(**fields):
print(type(fields))
for key, value in fields.items():
print(f"{key}: {value}")
describe(item="pen", quantity=3)<class 'dict'>
item: pen
quantity: 3Deux étoiles rassemblent les arguments passés par nom, et le résultat est un dictionnaire — des paires de nom et de valeur, ce qui est exactement sa forme.
Une étoile donne un tuple, deux donnent un dictionnaire. Retenez cette correspondance, le reste suit.
Paramètres qui ne peuvent être passés que par leur nom
Un paramètre placé après *args ne peut pas être passé par position, car *args prend pour lui tous les arguments positionnels restants. Il ne peut donc être passé que par son nom. On parle de paramètre keyword-only :
def total(*prices, discount=0.0):
return sum(prices) * (1 - discount)
print(total(100, 50))
print(total(100, 50, discount=0.1))
print(total(100, 50, 0.1))150.0
135.0
150.1Regardez la troisième ligne. 0.1 n'est pas devenu la remise — il est allé dans prices comme un prix de plus, et le résultat est faux sans aucune erreur. Pour passer discount, il faut écrire discount=0.1.
Vous pouvez aussi rendre un paramètre keyword-only sans vouloir de *args : mettez une étoile seule dans la liste des paramètres. Tout ce qui vient après est keyword-only :
def send(message, *, retry=False):
return f"{message} (retry={retry})"
print(send("hi", retry=True))
print(send("hi", True))hi (retry=True)
TypeError: send() takes 1 positional argument but 2 were given(Avant la deuxième ligne, Python affiche aussi quelques lignes de traceback ; seule la dernière est montrée ici.)
C'est très utile pour les réglages True/False : l'appelant doit écrire retry=True, donc quiconque lit l'appel voit à quoi sert le True.
L'ordre est imposé
Mise bout à bout, la règle complète est :
- Les paramètres simples en premier. Parmi eux, ceux sans valeur par défaut viennent avant ceux qui en ont une.
- Puis
*args(ou une étoile seule). - Puis les paramètres keyword-only — avec ou sans valeur par défaut, dans n'importe quel ordre.
- En dernier,
**kwargs.
def f(a, b=1, *args, c, d=2, **kwargs):
print(a, b, args, c, d, kwargs)
f(10, c=3)
f(10, 20, 30, 40, c=3, e=5)10 1 () 3 2 {}
10 20 (30, 40) 3 2 {'e': 5}c n'a pas de valeur par défaut, pourtant il vient après b=1, qui en a une, et c'est légal — parce que tout ce qui suit *args arrive par nom, donc il n'y a aucun doute sur la place de chaque valeur. La règle « les valeurs par défaut en dernier » ne s'applique qu'à la partie avant *args.
Ça paraît tatillon au début, et la raison est simple : Python doit savoir quel argument va où, et comme *args prend « tout le reste », aucun paramètre positionnel ne peut venir après lui.
Déballer dans un appel
Les étoiles peuvent aussi servir à l'appel, où elles font le travail inverse :
def order(item, quantity):
return f"{quantity} x {item}"
values = ["pen", 3]
fields = {"item": "bag", "quantity": 2}
print(order(*values))
print(order(**fields))3 x pen
2 x bag*values déballe la liste — c'est la même chose qu'écrire order(values[0], values[1]). **fields déballe le dictionnaire, et ses clés doivent correspondre aux noms des paramètres.
Dans une définition, une étoile signifie rassembler ; à l'appel, elle signifie déballer — le même symbole, dans deux sens.
Un exemple complet
invoice.py :
# Arguments, in the order Python expects them
def invoice(customer, *items, currency="unit", discount=0.0, **extra):
total = sum(price for _, price in items)
after = total * (1 - discount)
lines = [f"Invoice for {customer}"]
for name, price in items:
lines.append(f" {name:<10} {price:>8.2f} {currency}")
lines.append(f" {'subtotal':<10} {total:>8.2f} {currency}")
if discount:
lines.append(f" {'discount':<10} {discount * 100:>7.0f}%")
lines.append(f" {'total':<10} {after:>8.2f} {currency}")
for key, value in extra.items():
lines.append(f" note {key}: {value}")
return "\n".join(lines)
print(invoice("rafi", ("pen", 15.0), ("bag", 850.0)))
print()
print(invoice("dia", ("ink", 120.0), discount=0.1, reference="A-77"))Invoice for rafi
pen 15.00 unit
bag 850.00 unit
subtotal 865.00 unit
total 865.00 unit
Invoice for dia
ink 120.00 unit
subtotal 120.00 unit
discount 10%
total 108.00 unit
note reference: A-77Quatre points méritent d'être remarqués.
customer est obligatoire, tout le reste est facultatif. Une facture sans client n'a aucun sens, donc c'est un paramètre simple. Il peut ne pas y avoir d'articles du tout, d'où *items.
discount=0.0 n'est pas seulement une valeur par défaut, elle simplifie aussi une condition. La ligne if discount: utilise la règle de véracité du chapitre huit — 0.0 est faux, donc sans remise cette ligne n'est jamais affichée. Et comme discount vient après *items, c'est un paramètre keyword-only — il doit être passé sous la forme discount=0.1.
`extra absorbe ce que la fonction ne connaît pas.** reference="A-77" ne correspond à aucun paramètre, donc il atterrit dans extra` et s'affiche comme une note. C'est ainsi qu'une fonction accepte des champs auxquels personne n'a encore pensé.
for _, price in items est le déballage du chapitre quatorze, le tiret bas indiquant que le nom n'est pas nécessaire ici. items est un tuple de tuples, et chaque tuple interne est déballé.
Quand ça casse
SyntaxError: parameter without a default follows parameter with a default Un paramètre avec une valeur par défaut a été placé avant un paramètre obligatoire. Obligatoires d'abord, valeurs par défaut ensuite.
TypeError: order() takes 2 positional arguments but 3 were given Trop d'arguments. Le message indique à la fois combien la fonction en accepte et combien sont arrivés.
TypeError: order() got an unexpected keyword argument 'qty' Passé par nom, mais aucun paramètre ne porte ce nom. Vérifiez l'orthographe — ou sachez qu'une fonction avec **kwargs ne se serait pas plainte, elle aurait discrètement absorbé l'argument.
TypeError: order() got multiple values for argument 'item' Le même paramètre a été donné deux fois, une fois par position et une fois par nom — order("pen", item="bag").
La fonction tourne mais la réponse est dans le mauvais sens Les arguments ont été inversés, et aucune erreur n'est apparue parce que le code à l'intérieur fonctionne avec les deux valeurs. Passez par nom — c'est l'erreur silencieuse par laquelle ce chapitre a commencé.
*Déballage avec `, mais les nombres ne correspondent pas** Dans order(*values), values doit contenir exactement autant d'éléments que la fonction a de paramètres. Vérifiez avec print(len(values))`.
Étape 4 sur 6 — Prédire
Vérifiez votre compréhension
quantity a une valeur par défaut. Que s'affichent les deux lignes ?
def order(item, quantity=1):
return f"{quantity} x {item}"
print(order("pen"))
print(order("pen", 5))- A1 x pen 5 x pen
- B1 x pen 1 x pen
- CUn `TypeError`
- Dpen x 1 pen x 5
Quel genre de chose *things rassemble-t-il ?
def show(*things):
print(type(things))
print(len(things))
show(1, "two", 3.0)- A<class 'tuple'> 3
- B<class 'tuple'> 1
- C<class 'list'> 3
- D<class 'dict'> 3
Le paramètre avec une valeur par défaut est placé en premier. Que se passe-t-il ?
def order(quantity=1, item):
return item- AÇa fonctionne normalement
- BÇa fonctionne, mais l'appel `order("pen")` échoue
- CUn `SyntaxError` — un paramètre avec une valeur par défaut ne peut pas précéder un paramètre obligatoire
- DUn `TypeError`
Répondre nécessite un compte
Connectez-vous pour vérifier vos réponses
Les questions sont ci-dessus, et les résoudre de tête est ce qui compte vraiment. Connectez-vous pour voir les réponses, les explications et les indices à trois niveaux.
À vous de jouer
Écrivez un fichier appelé message.py contenant une seule fonction :
def notify(recipient, *lines, subject="Notice", urgent=False, **details)Elle doit renvoyer un message lisible plutôt que l'afficher, avec le destinataire, le sujet, chaque ligne sur une ligne distincte, et chaque paire de details comme une note en dessous. Si urgent vaut True, ajoutez quelque chose avant le sujet.
Appelez-la ensuite de quatre façons :
- Avec un destinataire seulement, sans aucune ligne
- Avec trois lignes, et tout le reste laissé à sa valeur par défaut
- Avec
urgent=Trueet unsubject, les deux par nom - En construisant une liste et un dictionnaire, puis en les déballant avec
*et**
Cassez-la ensuite trois fois exprès, et observez ce qui se passe à chaque fois :
- Déplacez le paramètre
subject="Notice"avantrecipient. Avant même que le fichier ne s'exécute, vous obtenezSyntaxError: parameter without a default follows parameter with a default. - Écrivez
notify("rafi", subjekt="hi"), avec une faute de frappe. Il n'y a aucune erreur :**detailsaccepte discrètement le mauvais nom lui aussi, doncsubjektdevient une note et le sujet reste"Notice". Affichez le message renvoyé et constatez-le vous-même. - Écrivez
notify("rafi", recipient="dia"). Vous obtenezTypeError: notify() got multiple values for argument 'recipient'.
Python attrape deux de ces trois erreurs et laisse passer la troisième — et c'est la plus dangereuse. C'est le prix de la commodité qu'offre **kwargs : personne ne vérifie plus l'orthographe. Reconnaissez ces messages une bonne fois, et vous les repérerez en quelques secondes ; vous saurez aussi où se cache l'erreur silencieuse.
Étape 6 sur 6
Défi — le quiz du chapitre
Dix questions, de la plus facile à la plus difficile. Les dernières sont difficiles exprès.
Connectez-vous pour faire le quiz