Functions
Structure and reuse your code effectively
Functions in Python: Organizing and Reusing Code
Functions are one of the fundamental concepts of programming. They let you group sequences of instructions into reusable blocks, organize code into logical components, and avoid repetition. In this tutorial, we will take an in-depth look at functions in Python, from basic syntax to advanced concepts.
What Is a Function?
A function is a named block of code that performs a specific task. Functions can accept input data (parameters), perform operations on that data, and return a result. They allow you to:
- Reuse code without having to rewrite it
- Simplify maintenance by compartmentalizing logic
- Improve readability by breaking a complex problem into smaller subproblems
- Make testing easier by isolating specific behaviors
Defining a Simple Function
In Python, functions are defined with the def keyword followed by the function name and parentheses that may contain parameters.
def saluer():
"""This function prints a simple greeting message."""
print("Bonjour, monde !")
# Calling the function
saluer() # Prints: Bonjour, monde !
Anatomy of a Function
- Definition: Starts with
def, followed by the function name and parentheses - Docstring: Documentation string (optional but recommended) enclosed in triple quotes
- Body: Indented code that makes up the function
returnstatement: Returns a value (optional)
Parameters and Arguments
Parameters allow functions to accept input data, making them more flexible and reusable.
Positional Parameters
def saluer_personne(prenom, nom):
"""Greets a person by their full name."""
print(f"Bonjour, {prenom} {nom} !")
# Call with positional arguments
saluer_personne("Marie", "Dupont") # Prints: Bonjour, Marie Dupont !
Default Parameters
Default parameters define a value that will be used if no argument is provided.
def saluer_avec_formule(nom, formule="Bonjour"):
"""Greets with a customizable phrase."""
print(f"{formule}, {nom} !")
# Function calls
saluer_avec_formule("Jean") # Uses the default phrase
# Prints: Bonjour, Jean !
saluer_avec_formule("Pierre", "Salut") # Overrides the default phrase
# Prints: Salut, Pierre !
Keyword Arguments
Keyword arguments let you explicitly specify which parameter receives which value.
def decrire_personne(nom, age, profession, ville):
"""Prints a description of a person."""
print(f"{nom}, {age} ans, est {profession} à {ville}.")
# Call with keyword arguments (different order from the definition)
decrire_personne(
ville="Paris",
profession="ingénieure",
nom="Sophie",
age=32
)
# Prints: Sophie, 32 ans, est ingénieure à Paris.
Variable Number of Arguments
Python lets you define functions that accept a variable number of arguments.
Variable Positional Arguments (*args)
def calculer_somme(*nombres):
"""Calculates the sum of all the numbers provided."""
resultat = 0
for nombre in nombres:
resultat += nombre
return resultat
# Calls with different numbers of arguments
print(calculer_somme(1, 2)) # 3
print(calculer_somme(1, 2, 3, 4, 5)) # 15
print(calculer_somme()) # 0
Variable Keyword Arguments (**kwargs)
def afficher_informations(**infos):
"""Prints all the information provided as key-value pairs."""
for cle, valeur in infos.items():
print(f"{cle}: {valeur}")
# Call with several keyword arguments
afficher_informations(
nom="Alice",
age=28,
email="alice@example.com"
)
# Prints:
# nom: Alice
# age: 28
# email: alice@example.com
Combining Argument Types
def fonction_complete(arg1, arg2, *args, kwarg1="défaut", **kwargs):
"""Demonstrates all possible argument types."""
print(f"arg1: {arg1}")
print(f"arg2: {arg2}")
print(f"args: {args}")
print(f"kwarg1: {kwarg1}")
print(f"kwargs: {kwargs}")
fonction_complete(1, 2, 3, 4, 5, kwarg1="personnalisé", x=10, y=20)
# Prints:
# arg1: 1
# arg2: 2
# args: (3, 4, 5)
# kwarg1: personnalisé
# kwargs: {'x': 10, 'y': 20}
Return Values
A function can return a value using the return statement. If there is no return statement, the function implicitly returns None.
Returning a Single Value
def carre(nombre):
"""Returns the square of a number."""
return nombre ** 2
resultat = carre(4)
print(resultat) # 16
Returning Multiple Values
def obtenir_dimensions():
"""Returns the width and height of a screen."""
largeur = 1920
hauteur = 1080
return largeur, hauteur # Returns a tuple
dimensions = obtenir_dimensions()
print(dimensions) # (1920, 1080)
# Unpacking the returned tuple
largeur, hauteur = obtenir_dimensions()
print(f"Largeur: {largeur}, Hauteur: {hauteur}") # Largeur: 1920, Hauteur: 1080
Returning Different Values Depending on Conditions
def analyser_nombre(n):
"""Analyzes a number and returns different information depending on its value."""
if n < 0:
return "négatif"
elif n == 0:
return "zéro"
else:
return "positif", "pair" if n % 2 == 0 else "impair"
# Calls with different values
print(analyser_nombre(-5)) # négatif
print(analyser_nombre(0)) # zéro
print(analyser_nombre(7)) # ('positif', 'impair')
print(analyser_nombre(8)) # ('positif', 'pair')
Early Exit with return
def diviser_securise(a, b):
"""Performs a safe division."""
if b == 0:
print("Division par zéro impossible")
return None # Early exit
return a / b
print(diviser_securise(10, 2)) # 5.0
print(diviser_securise(10, 0)) # None (after printing the error message)
Variable Scope and Namespaces
A variable’s scope determines where that variable is accessible in your code.
Local and Global Variables
# Global variable
x = 10
def modifier_variable():
# Local variable
y = 5
print(f"Dans la fonction, x = {x}") # Access to the global variable x
print(f"Dans la fonction, y = {y}")
modifier_variable()
print(f"Hors fonction, x = {x}")
# print(f"Hors fonction, y = {y}") # Error: y is not defined
Modifying a Global Variable
To modify a global variable inside a function, you must use the global keyword.
compteur = 0
def incrementer():
global compteur # Declares that we are using the global variable
compteur += 1
print(f"Compteur = {compteur}")
incrementer() # Compteur = 1
incrementer() # Compteur = 2
Nonlocal Variables (Closures)
In nested functions, the nonlocal keyword gives you access to variables from the enclosing function.
def creer_compteur():
"""Creates and returns a counting function."""
count = 0
def incrementer():
nonlocal count # Access to the enclosing function's variable
count += 1
return count
return incrementer
mon_compteur = creer_compteur()
print(mon_compteur()) # 1
print(mon_compteur()) # 2
print(mon_compteur()) # 3
Lambda Functions (Anonymous Functions)
Lambda functions are anonymous functions defined on a single line with a concise syntax.
Lambda Function Syntax
# Standard function
def carre(x):
return x ** 2
# Equivalent with lambda
carre_lambda = lambda x: x ** 2
print(carre(5)) # 25
print(carre_lambda(5)) # 25
Typical Use with Higher-Order Functions
Lambdas are often used with functions such as map(), filter(), and sorted().
# Using map() to transform a list
nombres = [1, 2, 3, 4, 5]
carres = list(map(lambda x: x ** 2, nombres))
print(carres) # [1, 4, 9, 16, 25]
# Using filter() to filter a list
nombres = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pairs = list(filter(lambda x: x % 2 == 0, nombres))
print(pairs) # [2, 4, 6, 8, 10]
# Using sorted() to sort a list
etudiants = [
{"nom": "Alice", "note": 85},
{"nom": "Bob", "note": 92},
{"nom": "Charlie", "note": 78}
]
etudiants_tries = sorted(etudiants, key=lambda etudiant: etudiant["note"], reverse=True)
for etudiant in etudiants_tries:
print(f"{etudiant['nom']}: {etudiant['note']}")
# Bob: 92
# Alice: 85
# Charlie: 78
Limitations of Lambda Functions
Lambda functions are limited to a single expression and cannot contain multiple statements or complex control structures. For more complex functions, it is better to use the def syntax.
Recursive Functions
A recursive function is a function that calls itself.
Basic Example: Factorial
def factorielle(n):
"""Calculates the factorial of n recursively."""
if n <= 1: # Base case
return 1
else: # Recursive call
return n * factorielle(n - 1)
print(factorielle(5)) # 120 (5 * 4 * 3 * 2 * 1)
Advanced Example: Traversing a Nested Structure
def parcourir_structure(structure, niveau=0):
"""Recursively traverses a nested data structure."""
# Print the current structure, indented according to the level
indentation = " " * niveau
print(f"{indentation}Niveau {niveau}: {structure}")
# If the structure is a dictionary or a list, traverse it recursively
if isinstance(structure, dict):
for cle, valeur in structure.items():
print(f"{indentation} Clé: {cle}")
parcourir_structure(valeur, niveau + 1)
elif isinstance(structure, list):
for i, item in enumerate(structure):
print(f"{indentation} Index: {i}")
parcourir_structure(item, niveau + 1)
# Usage example
structure_exemple = {
"personne": {
"nom": "Dupont",
"adresses": [
{"type": "domicile", "ville": "Paris"},
{"type": "travail", "ville": "Lyon"}
]
}
}
parcourir_structure(structure_exemple)
Recursion Limits
Python limits recursion depth to prevent stack overflows. By default, this limit is generally 1000 calls.
import sys
print(sys.getrecursionlimit()) # Prints the current limit (usually 1000)
# To change the limit (use with caution)
# sys.setrecursionlimit(2000)
To avoid deep recursion problems, some recursive algorithms can be rewritten iteratively or by using techniques such as memoization.
Function Decorators
Decorators are an advanced concept that lets you modify or extend the behavior of functions.
Basic Concept
def mon_decorateur(fonction):
def wrapper():
print("Avant l'appel de la fonction")
fonction()
print("Après l'appel de la fonction")
return wrapper
@mon_decorateur
def dire_bonjour():
print("Bonjour !")
dire_bonjour()
# Prints:
# Avant l'appel de la fonction
# Bonjour !
# Après l'appel de la fonction
Decorator That Preserves Arguments
def decorateur_avec_arguments(fonction):
def wrapper(*args, **kwargs):
print(f"Arguments: {args}, Kwargs: {kwargs}")
resultat = fonction(*args, **kwargs)
print(f"Résultat: {resultat}")
return resultat
return wrapper
@decorateur_avec_arguments
def addition(a, b):
return a + b
addition(3, 5)
# Prints:
# Arguments: (3, 5), Kwargs: {}
# Résultat: 8
Decorators with Parameters
def repeter(n):
def decorateur(fonction):
def wrapper(*args, **kwargs):
for _ in range(n):
resultat = fonction(*args, **kwargs)
return resultat
return wrapper
return decorateur
@repeter(3)
def dire_bonjour(nom):
print(f"Bonjour, {nom} !")
return nom
dire_bonjour("Alice")
# Prints 3 times:
# Bonjour, Alice !
Documentation and Type Annotations
Docstrings
Docstrings (documentation strings) let you document your functions.
def calculer_moyenne(nombres):
"""
Calculates the arithmetic mean of a list of numbers.
Args:
nombres (list): A list of numbers (int or float)
Returns:
float: The mean of the numbers
Raises:
ZeroDivisionError: If the list is empty
Examples:
>>> calculer_moyenne([1, 2, 3, 4, 5])
3.0
"""
return sum(nombres) / len(nombres)
Docstrings can be accessed with the help() function:
help(calculer_moyenne)
Type Annotations (Python 3.5+)
Type annotations improve readability and enable static code checking.
def saluer(nom: str, age: int = 30) -> str:
"""Returns a personalized greeting message."""
return f"Bonjour {nom}, vous avez {age} ans !"
# Annotations are stored in the __annotations__ attribute
print(saluer.__annotations__)
# {'nom': <class 'str'>, 'age': <class 'int'>, 'return': <class 'str'>}
For more complex types, use the typing module:
from typing import List, Dict, Tuple, Optional, Union, Callable
def traiter_donnees(
valeurs: List[int],
config: Dict[str, str],
limites: Tuple[int, int],
callback: Optional[Callable[[int], bool]] = None
) -> Union[int, str]:
"""Processes data with complex types."""
# Implementation...
return 0
Best Practices for Functions
General Principles
-
One function, one responsibility: Each function should have a single, clear purpose.
-
Descriptive names: Choose names that explain what the function does, not how it does it.
# Good def calculer_moyenne(nombres): return sum(nombres) / len(nombres) # Avoid def fonction1(liste): return sum(liste) / len(liste) -
Reasonable size: Functions should be relatively short (generally fewer than 20-30 lines).
-
Immutable default parameters: Avoid using mutable objects as default values.
# Problematic def ajouter_element(element, liste=[]): liste.append(element) return liste # Better approach def ajouter_element(element, liste=None): if liste is None: liste = [] liste.append(element) return liste -
Clear documentation: Use docstrings to explain the purpose, parameters, and return values.
Naming conventions
Follow PEP 8 for naming conventions:
- Use lowercase names separated by underscores (
snake_case) - Prefer verbs for functions that perform actions
- Be consistent in your conventions
# Good
def calculer_moyenne(nombres):
pass
def obtenir_utilisateur_par_id(user_id):
pass
def est_valide(donnee):
pass
Complete example: Putting functions to work
Here is an example that combines several function concepts to build a simple task management system:
from typing import List, Dict, Optional, Tuple, Callable
import time
# Custom types
Tache = Dict[str, any]
ListeTaches = List[Tache]
def creer_tache(
titre: str,
description: str = "",
priorite: int = 1
) -> Tache:
"""
Creates a new task with an automatic timestamp.
Args:
titre: The task title
description: A detailed description (optional)
priorite: Priority level (1-5, 5 being the most important)
Returns:
A dictionary representing the task
"""
return {
"id": int(time.time()),
"titre": titre,
"description": description,
"priorite": max(1, min(5, priorite)), # Ensures the priority is between 1 and 5
"termine": False,
"date_creation": time.strftime("%Y-%m-%d %H:%M:%S")
}
def ajouter_tache(
liste_taches: ListeTaches,
tache: Tache
) -> ListeTaches:
"""Adds a task to the list and returns the updated list."""
liste_taches.append(tache)
return liste_taches
def marquer_comme_termine(
liste_taches: ListeTaches,
tache_id: int
) -> Tuple[bool, str]:
"""
Marks a task as completed.
Returns:
A tuple (success, message)
"""
for tache in liste_taches:
if tache["id"] == tache_id:
tache["termine"] = True
return True, f"Tâche '{tache['titre']}' marquée comme terminée"
return False, "Tâche non trouvée"
def filtrer_taches(
liste_taches: ListeTaches,
termine: Optional[bool] = None,
priorite_min: int = 0
) -> ListeTaches:
"""
Filters tasks based on several criteria.
Args:
liste_taches: List of tasks to filter
termine: If specified, filters tasks by their status
priorite_min: Filters tasks with a priority greater than or equal to this value
Returns:
A new list containing the filtered tasks
"""
resultat = []
for tache in liste_taches:
# Check the task status if the parameter is specified
if termine is not None and tache["termine"] != termine:
continue
# Check the minimum priority
if tache["priorite"] < priorite_min:
continue
resultat.append(tache)
return resultat
def afficher_taches(
liste_taches: ListeTaches,
formatter: Callable[[Tache], str] = None
) -> None:
"""
Displays a list of tasks.
Args:
liste_taches: List of tasks to display
formatter: Optional function to format each task
"""
if not liste_taches:
print("Aucune tâche à afficher.")
return
# Default formatter
if formatter is None:
def formatter(tache):
statut = "✓" if tache["termine"] else "☐"
return f"{statut} [{tache['priorite']}] {tache['titre']}"
# Display each task
for i, tache in enumerate(liste_taches, 1):
print(f"{i}. {formatter(tache)}")
# Using our functions
if __name__ == "__main__":
mes_taches = []
# Add a few tasks
mes_taches = ajouter_tache(
mes_taches,
creer_tache("Apprendre les fonctions Python", "Tutoriel complet", 4)
)
mes_taches = ajouter_tache(
mes_taches,
creer_tache("Faire les courses")
)
mes_taches = ajouter_tache(
mes_taches,
creer_tache("Répondre aux emails", priorite=5)
)
# Display the tasks
print("Toutes les tâches:")
afficher_taches(mes_taches)
# Mark a task as completed
succes, message = marquer_comme_termine(mes_taches, mes_taches[1]["id"])
print(f"\nRésultat: {message}")
# Filter and display incomplete tasks with priority >= 4
taches_importantes = filtrer_taches(mes_taches, termine=False, priorite_min=4)
print("\nTâches importantes non terminées:")
afficher_taches(taches_importantes)
# Use a custom formatter
print("\nAffichage détaillé:")
afficher_taches(mes_taches, lambda t: f"{t['titre']} - Priorité: {t['priorite']} - Créée le: {t['date_creation']}")
Conclusion
Functions are one of the pillars of Python programming. They let you structure your code and make it more modular, more readable, and easier to maintain. By mastering the techniques presented in this tutorial, you will be able to:
- Break complex problems down into manageable subproblems
- Avoid code duplication
- Build libraries of reusable functions
- Write more expressive and maintainable code
In the next tutorial, we will explore modules and packages in Python, which will let you organize your functions and classes into logical, reusable units.
Need to review data structures? Check out our tutorial on data structures in Python.
Comments
Comments are powered by GitHub Discussions
Sign in with GitHub to join the discussion