Python & AI Tutorials Logo
Programmation Python

20. Paramètres et arguments de fonction

Dans le Chapitre 19, nous avons appris à définir et appeler des fonctions avec des paramètres de base. Maintenant, nous allons explorer en profondeur le système flexible de paramètres et d’arguments de Python. Comprendre ces mécanismes vous permet d’écrire des fonctions à la fois puissantes et faciles à utiliser.

20.1) Arguments positionnels et nommés

Lorsque vous appelez une fonction, vous pouvez passer des arguments de deux façons fondamentales : par position ou par nom (mot-clé/keyword).

20.1.1) Arguments positionnels

Les arguments positionnels sont associés aux paramètres en fonction de leur ordre. Le premier argument va au premier paramètre, le deuxième au deuxième paramètre, et ainsi de suite.

python
def calculate_discount(price, discount_percent):
    """Calculate the final price after applying a discount."""
    discount_amount = price * (discount_percent / 100)
    final_price = price - discount_amount
    return final_price
 
# Passage des arguments par position
result = calculate_discount(100, 20)
print(result)

Sortie :

80.0

Dans cet exemple, 100 est affecté à price et 20 à discount_percent uniquement en fonction de leurs positions dans l’appel de fonction.

L’ordre compte de manière critique avec les arguments positionnels :

python
# Exemple : nous voulons calculer un article à 100 $ avec 20 % de remise
 
# Bon ordre : price d'abord, puis discount
print(calculate_discount(100, 20))
 
# Mauvais ordre : discount d'abord, puis price
print(calculate_discount(20, 100))

Sortie :

80.0
-16.0

Lorsque vous inversez les arguments, Python ne sait pas que vous avez fait une erreur — il les affecte simplement dans l’ordre. Cela produit un résultat mathématiquement valide mais logiquement incorrect (un prix négatif !).

20.1.2) Arguments nommés

Les arguments nommés spécifient explicitement quel paramètre reçoit quelle valeur en utilisant le nom du paramètre suivi d’un signe égal et de la valeur. Cela rend votre code plus lisible et protège contre les erreurs d’ordre.

python
def create_user_profile(username, email, age):
    """Create a user profile with the given information."""
    profile = f"User: {username}\nEmail: {email}\nAge: {age}"
    return profile
 
# Utilisation d'arguments nommés
profile = create_user_profile(username="alice_smith", email="alice@example.com", age=28)
print(profile)

Sortie :

User: alice_smith
Email: alice@example.com
Age: 28

Avec les arguments nommés, l’ordre n’a pas d’importance :

python
# Même résultat, ordre différent
profile1 = create_user_profile(username="bob", email="bob@example.com", age=35)
profile2 = create_user_profile(age=35, username="bob", email="bob@example.com")
profile3 = create_user_profile(email="bob@example.com", age=35, username="bob")
 
# Les trois produisent des résultats identiques
print(profile1 == profile2 == profile3)

Sortie :

True

Cette flexibilité est particulièrement précieuse lorsqu’une fonction a de nombreux paramètres, ce qui permet de voir facilement quelle valeur correspond à quel paramètre.

20.1.3) Mélanger arguments positionnels et arguments nommés

Vous pouvez combiner les deux styles dans un seul appel de fonction, mais il y a une règle importante : les arguments positionnels doivent venir avant les arguments nommés.

python
def format_address(street, city, state, zip_code):
    """Format a mailing address."""
    return f"{street}\n{city}, {state} {zip_code}"
 
# Valide : arguments positionnels d'abord, puis arguments nommés
address = format_address("123 Main St", "Springfield", state="IL", zip_code="62701")
print(address)

Sortie :

123 Main St
Springfield, IL 62701

Ici, "123 Main St" et "Springfield" sont positionnels (affectés à street et city), tandis que state et zip_code sont spécifiés par nom.

Tenter de placer des arguments positionnels après des arguments nommés provoque une erreur :

python
# Invalide : argument positionnel après un argument nommé
# address = format_address(street="123 Main St", "Springfield", state="IL", zip_code="62701")
# SyntaxError: positional argument follows keyword argument

Python impose cette règle car une fois que vous commencez à utiliser des arguments nommés, il devient ambigu de savoir quel paramètre positionnel un argument non nommé, placé ensuite, devrait remplir.

20.1.4) Quand utiliser chaque style

Utilisez des arguments positionnels quand :

  • La fonction a peu de paramètres (généralement 1 à 3)
  • L’ordre des paramètres est évident et intuitif
  • La fonction est couramment utilisée et l’ordre est bien connu
python
# Évident et concis
print(len("hello"))
result = max(10, 20, 5)

Utilisez des arguments nommés quand :

  • La fonction a de nombreux paramètres
  • Le sens des paramètres n’est pas immédiatement évident
  • Vous voulez ignorer certains paramètres qui ont des valeurs par défaut (abordé ensuite)
  • Vous voulez rendre votre code auto-documenté
python
# Clair et explicite
user = create_user_profile(username="charlie", email="charlie@example.com", age=42)

20.2) Valeurs par défaut des paramètres

Les fonctions peuvent spécifier des valeurs par défaut pour des paramètres. Lorsqu’un appelant ne fournit pas d’argument pour un paramètre qui a une valeur par défaut, Python utilise la valeur par défaut à la place.

20.2.1) Définir des paramètres avec des valeurs par défaut

Les valeurs par défaut sont spécifiées dans la définition de la fonction en utilisant l’opérateur d’affectation :

python
def greet_user(name, greeting="Hello"):
    """Greet a user with a customizable greeting."""
    return f"{greeting}, {name}!"
 
# Utilisation de la salutation par défaut
print(greet_user("Alice"))
 
# Fournir une salutation personnalisée
print(greet_user("Bob", "Good morning"))
print(greet_user("Carol", greeting="Hi"))

Sortie :

Hello, Alice!
Good morning, Bob!
Hi, Carol!

Le paramètre greeting a une valeur par défaut de "Hello". Quand vous appelez greet_user("Alice"), Python utilise cette valeur par défaut. Quand vous fournissez un deuxième argument, il remplace la valeur par défaut.

20.2.2) Les paramètres avec valeurs par défaut doivent venir après les paramètres obligatoires

Python exige que les paramètres avec des valeurs par défaut apparaissent après tous les paramètres sans valeurs par défaut. Cette règle évite l’ambiguïté quant à la correspondance entre arguments et paramètres.

python
# Correct : paramètres obligatoires d'abord, puis défauts
def create_product(name, price, category="General", in_stock=True):
    """Create a product record."""
    return {
        "name": name,
        "price": price,
        "category": category,
        "in_stock": in_stock
    }
 
product = create_product("Laptop", 999.99)
print(product)

Sortie :

{'name': 'Laptop', 'price': 999.99, 'category': 'General', 'in_stock': True}

Tenter de placer un paramètre obligatoire après un paramètre ayant une valeur par défaut provoque une erreur de syntaxe :

python
# Invalide : paramètre obligatoire après un paramètre par défaut
# def invalid_function(name="Unknown", age):
#     return f"{name} is {age} years old"
# SyntaxError: non-default argument follows default argument

Cela a du sens : si name a une valeur par défaut mais que age n’en a pas, comment Python saurait-il si invalid_function(25) signifie name=25 avec age manquant, ou age=25 avec name utilisant sa valeur par défaut ? La règle élimine cette ambiguïté.

20.2.3) Usages pratiques des paramètres par défaut

Les paramètres par défaut sont excellents pour les fonctions où certains arguments changent rarement :

python
def calculate_shipping(weight, distance, express=False):
    """Calculate shipping cost based on weight and distance."""
    base_rate = 0.50 * weight + 0.10 * distance
    
    if express:
        base_rate *= 2  # Les frais de livraison express sont doublés
    
    return round(base_rate, 2)
 
# La plupart des expéditions sont standard
standard_cost = calculate_shipping(5, 100)
print(f"Standard: ${standard_cost}")
 
# Parfois, quelqu'un a besoin de l'express
express_cost = calculate_shipping(5, 100, express=True)
print(f"Express: ${express_cost}")

Sortie :

Standard: $12.5
Express: $25.0

Cette conception rend le cas le plus courant (livraison standard) simple à appeler, tout en prenant en charge le cas moins fréquent (express) lorsque c’est nécessaire.

20.2.4) Plusieurs valeurs par défaut et remplacement sélectif

Quand une fonction a plusieurs paramètres avec des valeurs par défaut, vous pouvez remplacer n’importe quelle combinaison en utilisant des arguments nommés :

python
def format_currency(amount, currency="USD", show_symbol=True, decimal_places=2):
    """Format a number as currency."""
    symbols = {"USD": "$", "EUR": "€", "GBP": "£", "JPY": "¥"}
    
    formatted = f"{amount:.{decimal_places}f}"
    
    if show_symbol and currency in symbols:
        formatted = f"{symbols[currency]}{formatted}"
    
    return formatted
 
# Utiliser tous les défauts
print(format_currency(42.5))
 
# Remplacer seulement la devise
print(format_currency(42.5, currency="EUR"))
 
# Remplacer plusieurs défauts
print(format_currency(42.5, currency="JPY", decimal_places=0))

Sortie :

$42.50
€42.50
¥42

Cette flexibilité permet aux appelants de personnaliser exactement ce dont ils ont besoin tout en gardant l’appel de fonction concis.

20.3) Listes d’arguments de longueur variable avec *args

Parfois, vous voulez qu’une fonction accepte un nombre quelconque d’arguments sans savoir à l’avance combien il y en aura. Python fournit *args à cet effet.

20.3.1) Comprendre *args

La syntaxe *args dans une liste de paramètres collecte tous les arguments positionnels supplémentaires dans un tuple. Le nom args est une convention (abréviation de « arguments »), mais vous pouvez utiliser n’importe quel nom de paramètre valide après l’astérisque.

python
def calculate_total(*numbers):
    """Calculate the sum of any number of values."""
    total = 0
    for num in numbers:
        total += num
    return total
 
# Fonctionne avec n'importe quel nombre d'arguments
print(calculate_total(10))
print(calculate_total(10, 20))
print(calculate_total(10, 20, 30, 40))
print(calculate_total())

Sortie :

10
30
100
0

À l’intérieur de la fonction, numbers est un tuple contenant tous les arguments positionnels passés à la fonction. Quand aucun argument n’est fourni, c’est un tuple vide.

20.3.2) Combiner des paramètres classiques avec *args

Vous pouvez avoir des paramètres classiques avant *args. Les paramètres classiques consomment les premiers arguments, et *args collecte le reste :

python
def create_team(team_name, *members):
    """Create a team with a name and any number of members."""
    member_list = ", ".join(members)
    return f"Team {team_name}: {member_list}"
 
# Le premier argument va à team_name, le reste va à members
print(create_team("Alpha", "Alice", "Bob"))
print(create_team("Beta", "Carol"))
print(create_team("Gamma", "Dave", "Eve", "Frank", "Grace"))

Sortie :

Team Alpha: Alice, Bob
Team Beta: Carol
Team Gamma: Dave, Eve, Frank, Grace

Le premier argument ("Alpha", "Beta", ou "Gamma") est affecté à team_name, et tous les arguments restants sont collectés dans le tuple members.

20.4) Paramètres uniquement nommés et paramètres **kwargs

Python fournit deux mécanismes supplémentaires pour gérer les arguments : les paramètres uniquement nommés et **kwargs pour collecter des arguments nommés arbitraires.

20.4.1) Paramètres uniquement nommés

Les paramètres uniquement nommés doivent être spécifiés en utilisant des arguments nommés — ils ne peuvent pas être passés de manière positionnelle. Vous les créez en les plaçant après un * ou après *args dans la liste de paramètres.

python
def create_account(username, *, email, age):
    """Create an account. Email and age must be specified by name."""
    return {
        "username": username,
        "email": email,
        "age": age
    }
 
# Correct : email et age spécifiés par nom
account = create_account("alice", email="alice@example.com", age=28)
print(account)
 
# Invalide : tentative de passer email et age de façon positionnelle
# account = create_account("bob", "bob@example.com", 30)
# TypeError: create_account() takes 1 positional argument but 3 were given

Sortie :

{'username': 'alice', 'email': 'alice@example.com', 'age': 28}

Le * dans la liste de paramètres agit comme un séparateur. Tout ce qui se trouve après doit être passé comme un argument nommé. C’est utile lorsque vous voulez forcer les appelants à être explicites sur certains paramètres, ce qui rend le code plus lisible et moins sujet aux erreurs.

Vous pouvez aussi combiner des paramètres classiques, *args, et des paramètres uniquement nommés :

python
def log_event(event_type, *details, severity="INFO", timestamp=None):
    """Log an event with optional details and metadata."""
    # Nous apprendrons le module datetime en détail au Chapitre 39,
    # mais pour l'instant, retenez simplement que ces lignes obtiennent l'heure actuelle
    # et la formatent sous forme de chaîne de caractères représentant un timestamp
    from datetime import datetime
    
    if timestamp is None:
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    
    details_str = " | ".join(details)
    return f"[{timestamp}] {severity}: {event_type} - {details_str}"
 
# event_type est positionnel, details sont collectés par *details,
# severity et timestamp sont keyword-only
print(log_event("Login", "User: alice", "IP: 192.168.1.1"))
print(log_event("Error", "Database connection failed", severity="ERROR"))

Sortie (le timestamp variera selon le moment où vous exécutez le code) :

[2025-12-18 19:29:16] INFO: Login - User: alice | IP: 192.168.1.1
[2025-12-18 19:29:16] ERROR: Error - Database connection failed

20.4.2) Comprendre **kwargs

La syntaxe **kwargs collecte tous les arguments nommés supplémentaires dans un dictionnaire. Comme args, le nom kwargs est conventionnel (abréviation de « keyword arguments »), mais vous pouvez utiliser n’importe quel nom valide après le double astérisque.

python
def create_product(**attributes):
    """Create a product with any number of attributes."""
    product = {}
    for key, value in attributes.items():
        product[key] = value
    return product
 
# Passez tous les arguments nommés que vous voulez
laptop = create_product(name="Laptop", price=999.99, brand="TechCorp", in_stock=True)
print(laptop)
 
phone = create_product(name="Phone", price=699.99, color="Black")
print(phone)

Sortie :

{'name': 'Laptop', 'price': 999.99, 'brand': 'TechCorp', 'in_stock': True}
{'name': 'Phone', 'price': 699.99, 'color': 'Black'}

À l’intérieur de la fonction, attributes est un dictionnaire dont les clés sont les noms des paramètres et les valeurs sont les arguments passés.

20.4.3) Combiner paramètres classiques, *args et **kwargs

Vous pouvez utiliser tous ces mécanismes ensemble, mais ils doivent apparaître dans un ordre spécifique :

  1. Paramètres positionnels classiques
  2. *args (si présent)
  3. Paramètres uniquement nommés (si présents)
  4. **kwargs (si présent)
python
def complex_function(required, *args, keyword_only, **kwargs):
    """Demonstrate all parameter types together."""
    print(f"Required: {required}")
    print(f"Args: {args}")
    print(f"Keyword-only: {keyword_only}")
    print(f"Kwargs: {kwargs}")
 
complex_function(
    "value1",           # required
    "value2", "value3", # args
    keyword_only="kw",  # keyword_only
    extra1="e1",        # kwargs
    extra2="e2"         # kwargs
)

Sortie :

Required: value1
Args: ('value2', 'value3')
Keyword-only: kw
Kwargs: {'extra1': 'e1', 'extra2': 'e2'}

Cette flexibilité est puissante, mais doit être utilisée avec discernement. La plupart des fonctions n’ont pas besoin de tous ces mécanismes.

20.4.4) Cas d’usage pratique : fonctions de configuration

Un usage courant de **kwargs consiste à créer des fonctions qui acceptent des options de configuration :

python
def connect_to_database(host, port, **options):
    """Connect to a database with flexible configuration options."""
    connection_string = f"Connecting to {host}:{port}"
    
    # Traiter toutes les options supplémentaires
    if options.get("ssl"):
        connection_string += " with SSL"
    
    if options.get("timeout"):
        connection_string += f" (timeout: {options['timeout']}s)"
    
    if options.get("pool_size"):
        connection_string += f" (pool size: {options['pool_size']})"
    
    return connection_string
 
# Connexion de base
print(connect_to_database("localhost", 5432))
 
# Avec SSL
print(connect_to_database("db.example.com", 5432, ssl=True))
 
# Avec plusieurs options
print(connect_to_database("db.example.com", 5432, ssl=True, timeout=30, pool_size=10))

Sortie :

Connecting to localhost:5432
Connecting to db.example.com:5432 with SSL
Connecting to db.example.com:5432 with SSL (timeout: 30s) (pool size: 10)

Ce modèle permet à la fonction d’accepter n’importe quel nombre de paramètres de configuration optionnels sans devoir tous les définir explicitement dans la liste de paramètres.

Positionnel

Positionnels en plus

Keyword-Only

Mots-clés en plus

Appel de fonction

Type de paramètre ?

Paramètres classiques

tuple *args

Paramètres keyword-only

dictionnaire **kwargs

Affectés par position

Collectés dans un tuple

Doivent utiliser le nom

Collectés dans un dictionnaire

20.5) Déballage des arguments lors de l’appel des fonctions

Tout comme *args et **kwargs collectent des arguments lorsqu’on définit des fonctions, vous pouvez utiliser * et ** pour déballer des collections lors de l’appel d’une fonction.

20.5.1) Déballer des séquences avec *

L’opérateur * déballe une séquence (liste, tuple, etc.) en arguments positionnels séparés :

python
def calculate_rectangle_area(width, height):
    """Calculate the area of a rectangle."""
    return width * height
 
# Au lieu de passer les arguments individuellement
dimensions = [5, 10]
area = calculate_rectangle_area(dimensions[0], dimensions[1])
print(area)
 
# Déballer la liste directement
area = calculate_rectangle_area(*dimensions)
print(area)

Sortie :

50
50

Quand vous écrivez *dimensions, Python déballe la liste [5, 10] en deux arguments séparés, comme si vous aviez écrit calculate_rectangle_area(5, 10).

Cela fonctionne avec n’importe quel itérable :

python
def format_name(first, middle, last):
    """Format a full name."""
    return f"{first} {middle} {last}"
 
# Déballage d'un tuple
name_tuple = ("John", "Q", "Public")
print(format_name(*name_tuple))
 
# Déballage d'une liste
name_list = ["Jane", "M", "Doe"]
print(format_name(*name_list))
 
# Même déballage d'une chaîne (chaque caractère devient un argument)
# Cela ne fonctionne que si la fonction attend le bon nombre d'arguments
def show_first_three(a, b, c):
    return f"{a}, {b}, {c}"
 
print(show_first_three(*"ABC"))

Sortie :

John Q Public
Jane M Doe
A, B, C

20.5.2) Déballer des dictionnaires avec **

L’opérateur ** déballe un dictionnaire en arguments nommés :

python
def create_user(username, email, age):
    """Create a user profile."""
    return f"User: {username}, Email: {email}, Age: {age}"
 
# Dictionnaire avec des clés correspondant aux noms des paramètres
user_data = {
    "username": "alice",
    "email": "alice@example.com",
    "age": 28
}
 
# Déballer le dictionnaire
profile = create_user(**user_data)
print(profile)

Sortie :

User: alice, Email: alice@example.com, Age: 28

Quand vous écrivez **user_data, Python déballe le dictionnaire en arguments nommés, équivalent à :

python
create_user(username="alice", email="alice@example.com", age=28)

Les clés du dictionnaire doivent correspondre aux noms des paramètres de la fonction, sinon vous obtiendrez une erreur :

python
# Invalide : la clé du dictionnaire ne correspond pas à un nom de paramètre
invalid_data = {"name": "bob", "email": "bob@example.com", "age": 30}
# profile = create_user(**invalid_data)
# TypeError: create_user() got an unexpected keyword argument 'name'

20.5.3) Combiner le déballage avec des arguments classiques

Vous pouvez mélanger des arguments déballés avec des arguments classiques :

python
def calculate_total(base_price, tax_rate, discount):
    """Calculate total price after tax and discount."""
    subtotal = base_price * (1 + tax_rate)
    total = subtotal * (1 - discount)
    return round(total, 2)
 
# Certains arguments classiques, certains déballés
pricing = [0.08, 0.10]  # tax_rate et discount
total = calculate_total(100, *pricing)
print(total)

Sortie :

97.2

Vous pouvez aussi déballer plusieurs collections dans un seul appel :

python
def create_full_address(street, city, state, zip_code, country):
    """Create a complete address."""
    return f"{street}, {city}, {state} {zip_code}, {country}"
 
street_address = ["123 Main St", "Springfield"]
location_details = ["IL", "62701", "USA"]
 
address = create_full_address(*street_address, *location_details)
print(address)

Sortie :

123 Main St, Springfield, IL 62701, USA

20.5.4) Exemple pratique : appels de fonctions flexibles

Le déballage est particulièrement utile lorsqu’on travaille avec des données issues de sources externes :

python
def send_email(recipient, subject, body, cc=None, bcc=None):
    """Send an email with optional CC and BCC."""
    message = f"To: {recipient}\nSubject: {subject}\n\n{body}"
    
    if cc:
        message += f"\nCC: {cc}"
    if bcc:
        message += f"\nBCC: {bcc}"
    
    return message
 
# Données d'e-mail provenant d'un fichier de configuration ou d'une base de données
email_config = {
    "recipient": "user@example.com",
    "subject": "Welcome",
    "body": "Thank you for signing up!",
    "cc": "manager@example.com"
}
 
# Déballer la configuration directement
result = send_email(**email_config)
print(result)

Sortie :

To: user@example.com
Subject: Welcome
 
Thank you for signing up!
CC: manager@example.com

Ce modèle facilite le fait de faire circuler des arguments de fonction sous forme de structures de données, ce qui est courant lors de la création d’API ou du traitement de fichiers de configuration.

opérateur *

opérateur **

Collection

Type de déballage

Déballage de séquence

Déballage de dictionnaire

List/Tuple → Args positionnels

Dict → Args nommés

Appel de fonction

20.6) Le piège des arguments par défaut mutables (pourquoi les valeurs par défaut de liste persistent)

L’un des pièges les plus notoires de Python implique l’utilisation d’objets mutables (comme les listes ou les dictionnaires) comme valeurs par défaut de paramètres. Comprendre ce problème est crucial pour écrire des fonctions correctes.

20.6.1) Le problème : des valeurs par défaut mutables partagées

Considérez cette fonction apparemment innocente :

python
def add_student(name, grades=[]):
    """Add a student with their grades."""
    grades.append(name)
    return grades
 
# Premier appel
students1 = add_student("Alice")
print(students1)
 
# Deuxième appel - on s'attend à une liste nouvelle
students2 = add_student("Bob")
print(students2)
 
# Troisième appel
students3 = add_student("Carol")
print(students3)

Sortie :

['Alice']
['Alice', 'Bob']
['Alice', 'Bob', 'Carol']

Ce comportement surprend beaucoup de programmeurs. Chaque appel à add_student() sans fournir d’argument grades utilise le même objet liste, pas une nouvelle. La liste persiste entre les appels de fonction, en accumulant les valeurs.

20.6.2) Pourquoi cela arrive : les valeurs par défaut sont créées une seule fois

La clé pour comprendre ce comportement est de savoir quand les valeurs par défaut sont créées. Python évalue les valeurs par défaut des paramètres une seule fois, au moment où la fonction est définie, et non à chaque appel de la fonction.

python
def demonstrate_default_creation():
    """Show when defaults are created."""
    print("Function defined!")
 
def use_default(value=demonstrate_default_creation()):
    """Use a default that calls a function."""
    return value
 
# Le message s'affiche quand la fonction est DÉFINIE, pas appelée

Sortie :

Function defined!

Quand Python rencontre la ligne def use_default, il évalue le paramètre par défaut value=demonstrate_default_creation(). Cela appelle demonstrate_default_creation(), qui affiche immédiatement "Function defined!". Les appels ultérieurs à use_default() ne réévaluent pas ce défaut, donc rien d’autre ne s’affiche.

Quand Python rencontre def add_student(name, grades=[]):, il crée un objet liste vide et le stocke comme valeur par défaut pour grades. Chaque appel suivant qui ne fournit pas d’argument grades utilise ce même objet liste.

Voici une démonstration plus claire en utilisant l’identité des objets :

python
def show_list_identity(items=[]):
    """Show that the same list object is reused."""
    print(f"List ID: {id(items)}")
    items.append("item")
    return items
 
# Chaque appel utilise le même objet liste (même ID)
show_list_identity()
show_list_identity()
show_list_identity()

Sortie :

List ID: 140234567890123
List ID: 140234567890123
List ID: 140234567890123

Les numéros d’ID exacts varieront selon votre système, mais notez que les trois appels montrent la même valeur d’ID, ce qui prouve qu’ils utilisent le même objet liste. La fonction id() renvoie un identifiant unique pour chaque objet en mémoire — lorsque les ID correspondent, c’est le même objet.

20.6.3) Le bon modèle : utiliser None par défaut

La solution standard consiste à utiliser None comme valeur par défaut et à créer un nouvel objet mutable dans la fonction :

python
def add_student_correct(name, grades=None):
    """Add a student with their grades (correct version)."""
    if grades is None:
        grades = []  # Créer une NOUVELLE liste à chaque fois
    
    grades.append(name)
    return grades
 
# Maintenant, chaque appel obtient sa propre liste
students1 = add_student_correct("Alice")
print(students1)
 
students2 = add_student_correct("Bob")
print(students2)
 
students3 = add_student_correct("Carol")
print(students3)

Sortie :

['Alice']
['Bob']
['Carol']

Ce modèle fonctionne parce que None est immuable et qu’une nouvelle liste est créée dans le corps de la fonction à chaque fois que grades est None.

20.6.4) Le même problème avec les dictionnaires

Ce problème affecte tous les types mutables, pas seulement les listes :

python
# WRONG: Dictionary default
def create_config_wrong(key, value, config={}):
    """Create a configuration (BUGGY VERSION)."""
    config[key] = value
    return config
 
config1 = create_config_wrong("theme", "dark")
print(config1)
 
config2 = create_config_wrong("language", "en")
print(config2)
 
print("---")
 
# CORRECT: None as default
def create_config_correct(key, value, config=None):
    """Create a configuration (CORRECT VERSION)."""
    if config is None:
        config = {}
    
    config[key] = value
    return config
 
config1 = create_config_correct("theme", "dark")
print(config1)
 
config2 = create_config_correct("language", "en")
print(config2)

Sortie :

{'theme': 'dark'}
{'theme': 'dark', 'language': 'en'}
---
{'theme': 'dark'}
{'language': 'en'}

20.6.5) Résumé : la règle d’or

N’utilisez jamais d’objets mutables (listes, dictionnaires, ensembles) comme valeurs par défaut de paramètres. Utilisez toujours None et créez l’objet mutable à l’intérieur de la fonction :

python
# ❌ WRONG
def function(items=[]):
    pass
 
# ✅ CORRECT
def function(items=None):
    if items is None:
        items = []
    # Maintenant, utilisez items en toute sécurité

Ce modèle garantit que chaque appel de fonction obtient son propre objet mutable indépendant, évitant des bugs mystérieux où des données fuient entre les appels.


Dans ce chapitre, nous avons exploré en profondeur le système flexible de paramètres et d’arguments de Python. Vous avez appris à utiliser des arguments positionnels et nommés, à fournir des valeurs par défaut, à gérer un nombre variable d’arguments avec *args et **kwargs, à déballer des collections lors de l’appel de fonctions, et à éviter le piège des arguments par défaut mutables.

Ces mécanismes vous donnent des outils puissants pour concevoir des interfaces de fonctions à la fois flexibles et faciles à utiliser. À mesure que vous écrirez davantage de fonctions, vous développerez une intuition quant aux modèles de paramètres qui conviennent le mieux à différentes situations. La clé est de trouver un équilibre entre flexibilité et clarté — faites en sorte que vos fonctions soient faciles à appeler correctement et difficiles à appeler incorrectement.


© 2025. Primesoft Co., Ltd.
support@primesoft.ai