Chapitre 16 : Arguments Flexibles (*args et **kwargs)
*args, **kwargs, Déballage
1. Quoi : *args et **kwargs
*args et **kwargs sont des conventions de syntaxe en Python qui permettent à une fonction d'accepter un nombre variable d'arguments.
*args(Arguments) : Permet de passer un nombre variable d'arguments positionnels. Python les regroupe dans un tuple.**kwargs(Keyword Arguments) : Permet de passer un nombre variable d'arguments nommés. Python les regroupe dans un dictionnaire.
Les noms args et kwargs sont une convention. C'est l'astérisque (*) et le double astérisque (**) qui sont importants.
2. Pourquoi : Créer des fonctions flexibles
Ces syntaxes sont utiles dans de nombreux scénarios :
- Créer des fonctions qui peuvent opérer sur un nombre indéterminé d'entrées (ex: une fonction
sum()qui additionne tous les nombres passés en argument). - Créer des "wrappers" ou des "décorateurs" qui doivent transmettre des arguments à une autre fonction sans connaître ces arguments à l'avance.
- Étendre les fonctionnalités d'une fonction existante sans casser la compatibilité avec l'ancienne version.
3. Comment : Utilisation de *args
*args collecte tous les arguments positionnels supplémentaires dans un tuple.
# 'a' et 'b' sont des paramètres positionnels normaux.
# *args collecte tout le reste.
def my_function(a, b, *args):
print(f"a: {a}")
print(f"b: {b}")
print(f"args: {args}") # args est un tuple
print("-" * 10)
my_function(1, 2)
# a: 1
# b: 2
# args: () (un tuple vide)
my_function(1, 2, 3, 4, 5)
# a: 1
# b: 2
# args: (3, 4, 5)
Exemple pratique : une fonction sum_all
def sum_all(*numbers):
"""Calculates the sum of all numbers passed as arguments."""
total = 0
for number in numbers: # 'numbers' est un tuple
total += number
return total
print(sum_all(1, 2, 3)) # 6
print(sum_all(10, 20, 30, 40)) # 100
print(sum_all()) # 0
4. Comment : Utilisation de **kwargs
**kwargs collecte tous les arguments nommés supplémentaires dans un dictionnaire.
def display_info(**kwargs):
print(f"kwargs: {kwargs}") # kwargs est un dictionnaire
for key, value in kwargs.items():
print(f" - {key}: {value}")
print("-" * 10)
display_info(name="Alice", age=30)
# kwargs: {'name': 'Alice', 'age': 30}
# - name: Alice
# - age: 30
display_info(user_id=123, status="active", theme="dark")
# kwargs: {'user_id': 123, 'status': 'active', 'theme': 'dark'}
# - user_id: 123
# - status: active
# - theme: dark
5. Combinaison de tous les types d'arguments
L'ordre des paramètres dans la définition d'une fonction doit être respecté :
- Arguments positionnels standards.
*args.- Arguments nommés standards (après
*args). **kwargs.
def kitchen_sink(a, b, *args, setting1="default", **kwargs):
print(f"a: {a}")
print(f"b: {b}")
print(f"args: {args}")
print(f"setting1: {setting1}")
print(f"kwargs: {kwargs}")
print("-" * 20)
kitchen_sink(1, 2, 3, 4, setting1="custom", user="Alice", score=99)
# a: 1
# b: 2
# args: (3, 4)
# setting1: custom
# kwargs: {'user': 'Alice', 'score': 99}
6. Déballage d'arguments (* et ** à l'appel)
On peut aussi utiliser * et ** lors de l'appel d'une fonction pour "déballer" une liste/tuple ou un dictionnaire en arguments.
Déballage avec *
Si vous avez une liste ou un tuple, vous pouvez le passer comme arguments positionnels à une fonction.
def add(a, b, c):
return a + b + c
my_list = [10, 20, 30]
# Au lieu de faire add(my_list[0], my_list[1], my_list[2])
# On peut "déballer" la liste :
result = add(*my_list)
print(result) # 60
Déballage avec **
Si vous avez un dictionnaire, vous pouvez le passer comme arguments nommés à une fonction. Les clés du dictionnaire doivent correspondre aux noms des paramètres de la fonction.
def create_user(name, age, city):
print(f"User: {name}, Age: {age}, City: {city}")
user_data = {
"name": "Bob",
"age": 42,
"city": "New York"
}
# Le dictionnaire est déballé en arguments nommés :
# name="Bob", age=42, city="New York"
create_user(**user_data)
Cette technique est extrêmement utile pour appeler des fonctions de manière dynamique à partir de données de configuration ou de résultats d'API.
Exercices :
Exercice 16 - Générateur de Balises HTML
Objectif
Cet exercice a pour but de vous faire utiliser *args et **kwargs pour créer une fonction flexible capable de générer des balises HTML.
Contexte
En HTML, une balise est composée d'un nom (ex: p, div, a) et peut avoir de multiples attributs (ex: class="main", id="intro", href="/"). Vous allez créer une fonction qui prend le nom de la balise, son contenu, et un nombre variable d'attributs pour générer la chaîne de caractères HTML correspondante.
Énoncé
-
Créez un nouveau fichier Python nommé
html_tag_generator.py. -
Définissez une fonction nommée
create_html_tagqui accepte les paramètres suivants dans cet ordre :tag_name: Le nom de la balise (ex: "p").content: Le contenu textuel de la balise.**attributes: Un dictionnaire pour capturer tous les attributs HTML nommés.
-
À l'intérieur de la fonction : a. Commencez par construire la partie ouvrante de la balise. Par exemple, si
tag_nameest "p", la base est<p.b. Itérez sur les paires clé-valeur du dictionnaire
attributes(**kwargs). Pour chaque attribut, ajouteznom_attribut="valeur_attribut"à la chaîne de la balise. - Exemple : Siattributesest{'class': 'text-bold', 'id': 'p1'}, vous devez ajouterclass="text-bold" id="p1". - N'oubliez pas l'espace avant chaque attribut.c. Fermez la balise ouvrante avec
>.d. Ajoutez le
content.e. Ajoutez la balise fermante, qui est
</+tag_name+>.f. Retournez la chaîne de caractères HTML complète.
-
Testez votre fonction avec les appels suivants et affichez les résultats :
- Une balise
psimple :create_html_tag("p", "This is a paragraph.") - Une balise
aavec des attributs :create_html_tag("a", "Click here", href="/home", target="_blank") - Une balise
divavec une classe et un id :create_html_tag("div", "This is a section.", id="main-section", class_name="container")- Note : Comme
classest un mot-clé réservé en Python, on utilise souventclass_namecomme nom de paramètre et on le traite spécialement. Pour cet exercice, vous pouvez générerclass_name="..."ou, en bonus, le convertir enclass="...".
- Note : Comme
- Une balise
Résultat Attendu
<p>This is a paragraph.</p>
<a href="/home" target="_blank">Click here</a>
<div id="main-section" class_name="container">This is a section.</div>
Bonus
Modifiez votre fonction pour qu'elle gère correctement l'attribut class. Si la fonction reçoit un argument nommé class_name, elle doit le transformer en un attribut class dans la balise HTML.
Résultat Attendu (avec bonus)
<p>This is a paragraph.</p>
<a href="/home" target="_blank">Click here</a>
<div id="main-section" class="container">This is a section.</div>
Cliquez ici pour voir un exemple de code de solution
# html_tag_generator.py
def create_html_tag(tag_name, content, **attributes):
"""
Generates an HTML tag with content and dynamic attributes.
Args:
tag_name (str): The name of the HTML tag (e.g., 'p', 'div').
content (str): The text content inside the tag.
**attributes: Keyword arguments representing HTML attributes.
Use 'class_name' for the 'class' attribute.
Returns:
str: The complete HTML tag as a string.
"""
# Commence la balise ouvrante
tag_parts = [f"<{tag_name}"]
# Gère le cas spécial de 'class' (Bonus)
if 'class_name' in attributes:
attributes['class'] = attributes.pop('class_name')
# Ajoute les attributs
for key, value in attributes.items():
tag_parts.append(f' {key}="{value}"')
# Termine la balise
tag_parts.append(f">{content}</{tag_name}>")
# Joint toutes les parties en une seule chaîne
return "".join(tag_parts)
# --- Tests ---
# Test 1: Balise p simple
p_tag = create_html_tag("p", "This is a paragraph.")
print(p_tag)
# Test 2: Balise a avec attributs
a_tag = create_html_tag("a", "Click here", href="/home", target="_blank")
print(a_tag)
# Test 3: Balise div avec class_name (Bonus)
div_tag = create_html_tag("div", "This is a section.", id="main-section", class_name="container")
print(div_tag)