Leçon 5/9 7 min

Les fonctions

Créez des blocs réutilisables avec def, paramètres, return, *args, **kwargs et les fonctions built-in.

def : créer une fonction

Une fonction est un bloc de code réutilisable. On la définit avec def :

def saluer(nom):
    print(f"Bonjour {nom} !")

saluer("Alice")  # Bonjour Alice !
saluer("Bob")    # Bonjour Bob !

Le mot-clé return permet à la fonction de renvoyer une valeur :

def additionner(a, b):
    return a + b

resultat = additionner(3, 5)
print(resultat)  # 8

Rappel : l'indentation (4 espaces) définit le corps de la fonction. Tout ce qui est indenté après def fait partie de la fonction.

Paramètres par défaut

On peut donner une valeur par défaut à un paramètre :

Prédisez avant de lire

Imaginez cette fonction : def ajouter(item, panier=[]): qui fait panier.append(item) puis return panier. On l'appelle d'abord avec ajouter("pomme"), puis avec ajouter("banane") (sans préciser de panier). Avant de dérouler : que renvoie ce deuxième appel, ["banane"] ou ["pomme", "banane"] ?

Voir la réponse

Il renvoie ["pomme", "banane"], ce qui surprend presque tout le monde. La valeur par défaut [] est évaluée une seule fois, au moment où Python définit la fonction, pas à chaque appel. Tous les appels qui n'apportent pas leur propre panier partagent donc la même liste, qui s'accumule. Preuve par le sandbox : ajouter("pomme") affiche ['pomme'], puis ajouter("banane") affiche ['pomme', 'banane']. C'est pour ça qu'on ne met jamais un objet mutable (liste, dict) comme valeur par défaut. Le bon idiome : def ajouter(item, panier=None): puis if panier is None: panier = [].

def saluer(nom, langue="fr"):
    if langue == "fr":
        print(f"Bonjour {nom} !")
    else:
        print(f"Hello {nom}!")

saluer("Alice")           # Bonjour Alice !
saluer("Bob", "en")       # Hello Bob!
saluer("Charlie", langue="en")  # Hello Charlie!

Les paramètres avec défaut doivent être après les paramètres obligatoires.

*args et **kwargs : paramètres flexibles

*args accepte un nombre variable d'arguments :

def moyenne(*notes):
    return sum(notes) / len(notes)

print(moyenne(15, 12, 18))      # 15.0
print(moyenne(10, 20, 14, 16))  # 15.0

**kwargs accepte des arguments nommés (comme un dictionnaire) :

def creer_profil(**infos):
    for cle, valeur in infos.items():
        print(f"{cle}: {valeur}")

creer_profil(nom="Alice", age=25, ville="Paris")

Résultat :

nom: Alice
age: 25
ville: Paris

Fonctions lambda

Une lambda est une fonction anonyme en une ligne :

# Fonction classique
def doubler(x):
    return x * 2

# Même chose en lambda
doubler = lambda x: x * 2
print(doubler(5))  # 10

# Utile pour le tri
utilisateurs = [("Alice", 25), ("Bob", 30), ("Charlie", 20)]
utilisateurs.sort(key=lambda u: u[1])
print(utilisateurs)
# [('Charlie', 20), ('Alice', 25), ('Bob', 30)]

Fonctions built-in utiles

Python fournit des dizaines de fonctions prêtes à l'emploi :

# len, range, enumerate
fruits = ["pomme", "banane", "cerise"]
print(len(fruits))  # 3

for i, fruit in enumerate(fruits):
    print(f"{i}: {fruit}")
# 0: pomme
# 1: banane
# 2: cerise

# zip : combiner des listes
noms = ["Alice", "Bob"]
ages = [25, 30]
for nom, age in zip(noms, ages):
    print(f"{nom} a {age} ans")
# Alice a 25 ans
# Bob a 30 ans

# map et filter
nombres = [1, 2, 3, 4, 5]
doubles = list(map(lambda x: x * 2, nombres))
pairs = list(filter(lambda x: x % 2 == 0, nombres))
print(doubles)  # [2, 4, 6, 8, 10]
print(pairs)    # [2, 4]

Annoter les types : les type hints

Tu l'as vu dans l'introduction : Python est dynamiquement typé, tu n'es jamais obligé de préciser le type d'une variable. Mais depuis Python 3.5, tu peux annoter tes variables et tes fonctions pour indiquer le type que tu attends. On appelle ça les type hints.

def addition(a: int, b: int) -> int:
    return a + b

print(addition(2, 3))   # 5

# On peut aussi annoter une variable
age: int = 25
nom: str = "Alice"
print(f"{nom} a {age} ans")

La syntaxe est simple : a: int pour un paramètre, et -> int après les parenthèses pour annoncer le type de retour.

Ces annotations ne sont pas vérifiées à l'exécution. Python les ignore complètement quand il fait tourner le code : si vous passez un mauvais type, ça marche quand même (ou ça plante plus loin). Une annotation est une indication, pas une garantie.

def addition(a: int, b: int) -> int:
    return a + b

# Python ne bronche pas, meme avec des chaines :
print(addition("a", "b"))   # ab

Annote tes fonctions dans les vrais projets. Les type hints ne changent rien à l'exécution, mais ils rendent le code plus lisible, permettent à ton éditeur de proposer l'autocomplétion et de repérer des erreurs, et un outil comme mypy peut vérifier tout le code avant qu'il tourne. C'est l'équivalent Python du typage vu en JavaScript et en PHP.

def — creating a function

A function is a reusable block of code. Define one with def:

def greet(name):
    print(f"Hello {name}!")

greet("Alice")  # Hello Alice!
greet("Bob")    # Hello Bob!

The return keyword lets the function send back a value:

def add(a, b):
    return a + b

result = add(3, 5)
print(result)  # 8

Reminder: indentation (4 spaces) defines the function body. Everything indented after def is part of the function.

Default parameters

You can give a parameter a default value:

Predict before reading

Imagine this function: def add_item(item, basket=[]): which does basket.append(item) then return basket. First call: add_item("apple"), then add_item("banana") (no basket provided). Before you scroll: what does the second call return — ["banana"] or ["apple", "banana"]?

See the answer

It returns ["apple", "banana"], which surprises almost everyone. The default value [] is evaluated only once, when Python defines the function, not on every call. Every call that doesn't provide its own basket shares the same list, which keeps growing. Proof from the sandbox: add_item("apple") prints ['apple'], then add_item("banana") prints ['apple', 'banana']. That's why you should never use a mutable object (list, dict) as a default value. The standard idiom: def add_item(item, basket=None): then if basket is None: basket = [].

def greet(name, language="en"):
    if language == "en":
        print(f"Hello {name}!")
    else:
        print(f"Bonjour {name} !")

greet("Alice")             # Hello Alice!
greet("Bob", "fr")         # Bonjour Bob !
greet("Charlie", language="fr")  # Bonjour Charlie !

Parameters with defaults must come after required parameters.

*args and **kwargs — flexible parameters

*args accepts a variable number of arguments:

def average(*grades):
    return sum(grades) / len(grades)

print(average(15, 12, 18))      # 15.0
print(average(10, 20, 14, 16))  # 15.0

**kwargs accepts named arguments (like a dictionary):

def create_profile(**info):
    for key, value in info.items():
        print(f"{key}: {value}")

create_profile(name="Alice", age=25, city="Paris")

Result:

name: Alice
age: 25
city: Paris

Lambda functions

A lambda is an anonymous one-line function:

# Regular function
def double(x):
    return x * 2

# Same thing as lambda
double = lambda x: x * 2
print(double(5))  # 10

# Useful for sorting
users = [("Alice", 25), ("Bob", 30), ("Charlie", 20)]
users.sort(key=lambda u: u[1])
print(users)
# [('Charlie', 20), ('Alice', 25), ('Bob', 30)]

Useful built-in functions

Python provides dozens of ready-to-use functions:

# len, range, enumerate
fruits = ["apple", "banana", "cherry"]
print(len(fruits))  # 3

for i, fruit in enumerate(fruits):
    print(f"{i}: {fruit}")
# 0: apple
# 1: banana
# 2: cherry

# zip — combine lists
names = ["Alice", "Bob"]
ages = [25, 30]
for name, age in zip(names, ages):
    print(f"{name} is {age} years old")
# Alice is 25 years old
# Bob is 30 years old

# map and filter
numbers = [1, 2, 3, 4, 5]
doubles = list(map(lambda x: x * 2, numbers))
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(doubles)  # [2, 4, 6, 8, 10]
print(evens)    # [2, 4]

Annotating types: type hints

As you saw in the introduction, Python is dynamically typed: you never have to specify a variable's type. But since Python 3.5, you can annotate your variables and functions to state the type you expect. These are called type hints.

def add(a: int, b: int) -> int:
    return a + b

print(add(2, 3))   # 5

# You can annotate a variable too
age: int = 25
name: str = "Alice"
print(f"{name} is {age} years old")

The syntax is simple: a: int for a parameter, and -> int after the parentheses to declare the return type.

These annotations are not checked at runtime. Python ignores them entirely when running your code: if you pass the wrong type, it still runs (or crashes later). An annotation is a hint, not a guarantee.

def add(a: int, b: int) -> int:
    return a + b

# Python does not complain, even with strings:
print(add("a", "b"))   # ab

Annotate your functions in real projects. Type hints change nothing at runtime, but they make code more readable, let your editor offer autocompletion and catch mistakes, and a tool like mypy can check the whole codebase before it runs. It's the Python equivalent of the typing we saw in JavaScript and PHP.

🎯 Pratique

S'entraîner (clique pour ouvrir) :

Prompt IA
Avec l'IA

Copiez ce prompt dans Claude ou ChatGPT :

Écris une fonction Python qui prend une liste de nombres et renvoie un dictionnaire avec la moyenne, le minimum, le maximum et la médiane. Utilise *args pour accepter les nombres directement.
💬 Ré-explique sans regarder
Ré-explique sans regarder

Sans relire la réponse de l'IA : avec tes mots, quelle est la différence entre *args et **kwargs, et que reçoit la fonction dans chaque cas ?

Une bonne explication dit : *args collecte les arguments positionnels en trop dans un tuple (ex. moyenne(15, 12, 18) donne notes = (15, 12, 18)) ; **kwargs collecte les arguments nommés en trop dans un dict (ex. creer_profil(nom="Alice", age=25) donne infos = {"nom": "Alice", "age": 25}). Les deux servent à accepter un nombre variable d'arguments.
⚖️ Juge le code de l'IA
Accepter ou rejeter le code de l'IA

L'IA te propose cette fonction qui ajoute un élément à une liste. Ton rôle de relecteur : l'accepter telle quelle ou la rejeter, et dire pourquoi.

def ajouter(element, panier=[]):
    panier.append(element)
    return panier
À rejeter : c'est le piège classique du paramètre par défaut mutable. La liste [] n'est créée qu'une seule fois, à la définition de la fonction, puis réutilisée à chaque appel sans argument. Résultat : ajouter(1) renvoie [1], puis ajouter(2) renvoie [1, 2] et non [2]. Le correctif standard : def ajouter(element, panier=None) puis if panier is None: panier = [] au début du corps.
🧠 Rappel libre
Rappel libre

Sans remonter dans la leçon : quelle est la différence entre return et print() dans une fonction, et dans quel type d'objet *args regroupe-t-il les arguments ?

return renvoie une valeur à l'appelant, qu'on peut stocker dans une variable et réutiliser ; print() ne fait qu'afficher dans le terminal et renvoie None. Une fonction sans return renvoie d'ailleurs None. Et *args regroupe les arguments positionnels supplémentaires dans un tuple.
🔧 Débugue le code

Symptôme : le deuxième appel ajouter("banane") devrait renvoyer ['banane'], mais il renvoie ['pomme', 'banane']. Répare la fonction, puis exécute.

def ajouter(item, panier=[]):
    panier.append(item)
    return panier

print(ajouter("pomme"))
print(ajouter("banane"))
La cause : la valeur par défaut [] est créée une seule fois, à la définition de la fonction, et partagée entre tous les appels qui n'apportent pas leur propre liste : elle s'accumule. La correction (idiome standard) : déclarer panier=None, puis en première ligne if panier is None: panier = []. Ne jamais utiliser un objet mutable (liste, dict) comme valeur par défaut.
Quel mot-clé permet à une fonction de renvoyer une valeur ?
Que fait *args dans une définition de fonction ?
Que renvoie enumerate(["a", "b", "c"]) ?
Prochaine étape

Vos fonctions tournent, mais elles oublient tout dès que le programme s'arrête. Pour garder les données, on apprend à lire et écrire des fichiers, puis à importer des modules et installer des packages avec pip.

Leçon 6 : Classes et objets →

Une erreur dans cette leçon, un passage flou, une question ? Écrivez-moi : chaque retour améliore ce cours.

Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement