Fonctions avancées : paramètres et annotations
Vous savez écrire une fonction avec des paramètres et un return. Voyons les outils qui rendent les fonctions plus souples, plus sûres et plus lisibles.
Un nombre variable d’arguments : *args
def total(*montants):
"""Additionne autant de montants que l'on veut."""
# montants est un tuple contenant tous les arguments positionnels
return sum(montants)
print(total(10, 20)) # 30
print(total(5, 5, 5, 5)) # 20
valeurs = [1, 2, 3]
print(total(*valeurs)) # 6 : * déballe la liste en arguments
Le nom args est une convention ; c’est l’étoile qui compte.
Des options nommées : **kwargs
def creer_utilisateur(nom, **infos):
# infos est un dictionnaire des arguments nommés supplémentaires
fiche = {"nom": nom}
fiche.update(infos)
return fiche
print(creer_utilisateur("Lina", ville="Lille", age=31))
# {'nom': 'Lina', 'ville': 'Lille', 'age': 31}
options = {"ville": "Brest"}
print(creer_utilisateur("Yanis", **options)) # ** déballe un dictionnaire
L’ordre dans une signature est toujours : paramètres normaux, *args, paramètres nommés, **kwargs.
Forcer les arguments nommés
Un * seul dans la signature oblige à nommer les paramètres qui suivent. C’est précieux pour les options booléennes, illisibles en positionnel :
def exporter(donnees, *, separateur=";", entete=True):
...
exporter(lignes, separateur=",") # clair
# exporter(lignes, ",", False) # TypeError : arguments nommés obligatoires
Le piège des valeurs par défaut mutables
Une valeur par défaut est créée une seule fois, au moment où la fonction est définie. Avec une liste, elle est donc partagée entre les appels :
def ajouter(article, panier=[]): # À ÉVITER
panier.append(article)
return panier
print(ajouter("pomme")) # ['pomme']
print(ajouter("poire")) # ['pomme', 'poire'] : surprise !
def ajouter(article, panier=None): # version correcte
if panier is None:
panier = []
panier.append(article)
return panier
Les annotations de type
Les annotations indiquent les types attendus. Python ne les vérifie pas à l’exécution, mais votre éditeur et des outils comme mypy s’en servent pour détecter des erreurs, et elles documentent le code :
def moyenne(valeurs: list[float], arrondi: int = 2) -> float | None:
"""Renvoie la moyenne, ou None si la liste est vide."""
if not valeurs:
return None
return round(sum(valeurs) / len(valeurs), arrondi)
Depuis Python 3.9, on écrit list[float] ou dict[str, int] directement ; depuis 3.10, float | None signifie « un float ou None ». Depuis Python 3.14, les annotations ne sont évaluées que lorsqu’on les consulte : une fonction peut être annotée avec une classe définie plus bas dans le fichier, sans guillemets (avant 3.14, cela provoquait une NameError).
À retenir
*args→ tuple,**kwargs→ dictionnaire ;*et**déballent aussi à l’appel.*seul rend les paramètres suivants obligatoirement nommés.- Utilisez
Nonecomme valeur par défaut pour les objets modifiables. - Annotez vos fonctions : c’est de la documentation vérifiable par les outils.
Exercices
Les solutions et les tests restent sur le serveur. Dans l'espace étudiant, ton code est exécuté et corrigé, et le tuteur donne des indices avant la solution.
Moyenne pondérée flexible
Écrivez
moyenne(*notes, arrondi=1)qui renvoie la moyenne des notes passées en arguments, arrondie àarrondidécimales. Si aucune note n’est donnée, renvoyezNone. Annotez la fonction.def moyenne(*notes: float, arrondi: int = 1) -> float | None: ...Corriger le défaut mutable
La fonction
ajouter_tagci-dessous partage sa liste entre les appels. Corrigez-la pour que chaque appel sanstagsparte d’une liste vide neuve, et rendezuniqueobligatoirement nommé (avec*). Siuniquevaut True, le tag n’est pas ajouté s’il est déjà présent.def ajouter_tag(tag, tags=[], unique=False): if unique and tag in tags: return tags tags.append(tag) return tagsContenu de kwargs
Avec
def f(a, *args, **kwargs): return kwargs, que renvoief(1, 2, 3, x=4)?- (2, 3)
- {'x': 4}
- {'a': 1, 'x': 4}
- [2, 3, 4]
Quiz de fin de leçon
Que se passe-t-il si l’on appelle
f(1, 2)avecdef f(a, *, b=0)?- b vaut 2
- TypeError
- a vaut (1, 2)
Les annotations de type sont-elles vérifiées par Python à l’exécution ?
- Oui, toujours
- Non, elles servent aux outils et à la documentation
- Seulement pour les int
Pourquoi écrire
panier=Noneplutôt quepanier=[]?- C'est plus rapide
- La liste par défaut serait partagée entre tous les appels
- [] est interdit en défaut
La suite du cours
- Fonctions comme objets, lambda et générateurs20 min
- Modules, paquets et bibliothèque standard15 min
- Classes et objets20 min
- Héritage, dataclasses et exceptions personnalisées20 min