Advanced functions: parameters and annotations
You know how to write a function with parameters and a return. Let’s look at the tools that make functions more flexible, safer and more readable.
A variable number of arguments: *args
def total(*montants):
"""Add up as many amounts as you like."""
# montants is a tuple containing all the positional arguments
return sum(montants)
print(total(10, 20)) # 30
print(total(5, 5, 5, 5)) # 20
valeurs = [1, 2, 3]
print(total(*valeurs)) # 6: * unpacks the list into arguments
The name args is a convention; it is the star that matters.
Named options: **kwargs
def creer_utilisateur(nom, **infos):
# infos is a dictionary of the extra keyword arguments
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)) # ** unpacks a dictionary
The order in a signature is always: regular parameters, *args, keyword parameters, **kwargs.
Forcing keyword arguments
A lone * in the signature forces you to name the parameters that follow. This is valuable for boolean options, which are unreadable when positional:
def exporter(donnees, *, separateur=";", entete=True):
...
exporter(lignes, separateur=",") # clear
# exporter(lignes, ",", False) # TypeError: keyword arguments required
The mutable default value trap
A default value is created only once, when the function is defined. With a list, it is therefore shared between calls:
def ajouter(article, panier=[]): # AVOID
panier.append(article)
return panier
print(ajouter("pomme")) # ['pomme']
print(ajouter("poire")) # ['pomme', 'poire']: surprise!
def ajouter(article, panier=None): # correct version
if panier is None:
panier = []
panier.append(article)
return panier
Type annotations
Annotations indicate the expected types. Python does not check them at runtime, but your editor and tools like mypy use them to detect errors, and they document the code:
def moyenne(valeurs: list[float], arrondi: int = 2) -> float | None:
"""Return the average, or None if the list is empty."""
if not valeurs:
return None
return round(sum(valeurs) / len(valeurs), arrondi)
Since Python 3.9, you write list[float] or dict[str, int] directly; since 3.10, float | None means “a float or None”. Since Python 3.14, annotations are only evaluated when you inspect them: a function can be annotated with a class defined further down in the file, without quotes (before 3.14, this caused a NameError).
Key takeaways
*args→ tuple,**kwargs→ dictionary;*and**also unpack in a call.- A lone
*makes the following parameters keyword-only. - Use
Noneas the default value for mutable objects. - Annotate your functions: it is documentation that tools can verify.
Exercises
Solutions and tests stay on the server. In the student space, your code is run and checked, and the tutor gives hints before the solution.
Flexible weighted average
Write
moyenne(*notes, arrondi=1)that returns the average of the grades passed as arguments, rounded toarrondidecimal places. If no grade is given, returnNone. Annotate the function.def moyenne(*notes: float, arrondi: int = 1) -> float | None: ...Fix the mutable default
The
ajouter_tagfunction below shares its list between calls. Fix it so that each call withouttagsstarts from a fresh empty list, and makeuniquekeyword-only (with*). Ifuniqueis True, the tag is not added if it is already present.def ajouter_tag(tag, tags=[], unique=False): if unique and tag in tags: return tags tags.append(tag) return tagsContents of kwargs
With
def f(a, *args, **kwargs): return kwargs, what doesf(1, 2, 3, x=4)return?- (2, 3)
- {'x': 4}
- {'a': 1, 'x': 4}
- [2, 3, 4]
End-of-lesson quiz
What happens if you call
f(1, 2)withdef f(a, *, b=0)?- b is 2
- TypeError
- a is (1, 2)
Does Python check type annotations at runtime?
- Yes, always
- No, they are for tools and documentation
- Only for int
Why write
panier=Nonerather thanpanier=[]?- It is faster
- The default list would be shared between all calls
- [] is forbidden as a default
The rest of the course
- Functions as Objects, Lambdas and Generators20 min
- Modules, Packages and the Standard Library15 min
- Classes and Objects20 min
- Inheritance, Dataclasses and Custom Exceptions20 min