Paramètres dynamiques de fonction, espaces de noms et portée en Python

Cet article explore les aspects avancés des fonctions en Python, notamment les paramètres dynamiques, les annotations, les espaces de noms et la portée.

Paramètres dynamiques

Python permet de définir des fonctions qui acceptent un nombre variable d'arguments. Ces paramètres sont précédés de * pour agréger les arguments positionnels en un tuple, et de ** pour agréger les arguments par mot-clé en un dictionnaire.

*args

Le paramètre *args permet à une fonction d'accepter un nombre indéterminé d'arguments positionnels. Ces arguments sont regroupés dans un tuple.


def traiter_arguments(*args):
    print("Arguments reçus sous forme de tuple:", args)
    # Pour imprimer chaque argument individuellement :
    print("Arguments individuels:")
    for arg in args:
        print(arg)

traiter_arguments(1, 2, 3)
# Output:
# Arguments reçus sous forme de tuple: (1, 2, 3)
# Arguments individuels:
# 1
# 2
# 3

def afficher_args_desagregues(*args):
    print(*args) # L'astérisque ici désagrège le tuple pour l'impression

afficher_args_desagregues(10, 20, 30)
# Output: 10 20 30

**kwargs

Le paramètre **kwargs permet à une fonction d'accepter un nombre indéterminé d'arguments par mot-clé. Ces arguments sont regroupés dans un dictionnaire.


def traiter_kwargs(**kwargs):
    print("Arguments par mot-clé reçus sous forme de dictionnaire:", kwargs)
    # Pour imprimer chaque clé et valeur :
    print("Paires clé-valeur:")
    for cle, valeur in kwargs.items():
        print(f"{cle}: {valeur}")

traiter_kwargs(nom="Alice", age=30, ville="Paris")
# Output:
# Arguments par mot-clé reçus sous forme de dictionnaire: {'nom': 'Alice', 'age': 30, 'ville': 'Paris'}
# Paires clé-valeur:
# nom: Alice
# age: 30
# ville: Paris

def afficher_kwargs_cles_desagreguees(**kwargs):
    print(*kwargs) # Désagrège les clés du dictionnaire pour l'impression

afficher_kwargs_cles_desagreguees(a=1, b=2, c=3)
# Output: a b c

Ordre des paramètres dans une définition de fonction :

L'ordre correct pour combiner différents types de paramètres est le suivant : arguments positionnels, *args, arguments par défaut, **kwargs.


def fonction_complete(arg_positionnel1, arg_positionnel2, *args_supplementaires, arg_par_defaut='valeur_defaut', **kwargs_supplementaires):
    print(f"Positionnel 1: {arg_positionnel1}")
    print(f"Positionnel 2: {arg_positionnel2}")
    print(f"Arguments supplémentaires (*args): {args_supplementaires}")
    print(f"Argument par défaut: {arg_par_defaut}")
    print(f"Arguments par mot-clé (**kwargs): {kwargs_supplementaires}")

fonction_complete(10, 20, 30, 40, 50, arg_par_defaut="nouvelle_valeur", cle1="valeur1", cle2="valeur2")
# Output:
# Positionnel 1: 10
# Positionnel 2: 20
# Arguments supplémentaires (*args): (30, 40, 50)
# Argument par défaut: nouvelle_valeur
# Arguments par mot-clé (**kwargs): {'cle1': 'valeur1', 'cle2': 'valeur2'}

fonction_complete(1, 2, 3, 4, cle_specifique="test")
# Output:
# Positionnel 1: 1
# Positionnel 2: 2
# Arguments supplémentaires (*args): (3, 4)
# Argument par défaut: valeur_defaut
# Arguments par mot-clé (**kwargs): {'cle_specifique': 'test'}

Combinaison de *args et **kwargs

Utilisés ensemble, *args et **kwargs permettent de passer un nombre quelconque d'arguments positionnels et par mot-clé à une fonction.


def fonction_flexible(*args, **kwargs):
    print("Tuple d'arguments positionnels:", args)
    print("Dictionnaire d'arguments par mot-clé:", kwargs)

fonction_flexible(1, 2, 'trois', cle_a=100, cle_b='deux-cent')
# Output:
# Tuple d'arguments positionnels: (1, 2, 'trois')
# Dictionnaire d'arguments par mot-clé: {'cle_a': 100, 'cle_b': 'deux-cent'}

Attention aux valeurs par défaut mutables

Il est crucial de faire attention lorsque la valeur par défaut d'un paramètre est un type de données mutable (comme une liste ou un dictionnaire). La valeur par défaut n'est évaluée qu'une seule fois lors de la définition de la fonction.


def ajouter_element(element, ma_liste=[]):
    ma_liste.append(element)
    return ma_liste

print(ajouter_element(1)) # Première appelée
print(ajouter_element(2)) # Deuxième appelée
print(ajouter_element(3)) # Troisième appelée
# Output:
# [1]
# [1, 2]
# [1, 2, 3]

# Pour obtenir un comportement attendu où chaque appel crée une nouvelle liste:
def ajouter_element_correct(element, ma_liste=None):
    if ma_liste is None:
        ma_liste = []
    ma_liste.append(element)
    return ma_liste

print(ajouter_element_correct(1))
print(ajouter_element_correct(2))
print(ajouter_element_correct(3))
# Output:
# [1]
# [2]
# [3]

Annotations de fonction

Les annotations de fonction sont un moyen d'ajouter des métadonnées aux paramètres et aux valeurs de retour d'une fonction. Elles ne sont pas exécutées par Python mais peuvent être utilisées par des outils externes (comme des linters ou des frameworks de documentation).


def saluer(nom: str, age: int) -> str:
    """
    Cette fonction salue une personne et retourne un message.

    :param nom: Le nom de la personne.
    :param age: L'âge de la personne.
    :return: Un message de salutation.
    """
    return f"Bonjour {nom}, vous avez {age} ans."

# Les annotations sont accessibles via __annotations__
print(saluer.__annotations__)
# Output: {'nom': <class>, 'age': <class>, 'return': <class>}

print(saluer("Bob", 25))
# Output: Bonjour Bob, vous avez 25 ans.
</class></class></class>

Fonctions imbriquées et portée

Python supporte les fonctions imbriquées, où une fonction est définie à l'intérieur d'une autre fonction. L'accès aux variables est régi par les règles de portée.

Espaces de noms (Namespaces)

Un espace de noms est une collection de noms (variables, fonctions, classes, etc.) qui sont uniques dans ce contexte. Python possède plusieurs espaces de noms :

  • Espace de noms intégré (Built-in) : Contient les fonctions et exceptions prédéfinies de Python (ex: print, len, ValueError).
  • Espace de noms global (Global) : Défini au niveau du module courant. Les variables définies en dehors de toute fonction appartiennent à cet espace.
  • Espace de noms local (Local) : Créé lorsqu'une fonction est appelée. Il contient les variables définies à l'intérieur de cette fonction.

Règles de portée (Scope)

L'ordre de recherche des noms suit la règle LEGB :

  1. Local : L'espace de noms de la fonction courante.
  2. Enclosign function locals : Les espaces de noms des fonctions englobantes (pour les fonctions imbriquées).
  3. Global : L'espace de noms du module courant.
  4. Built-in : L'espace de noms intégré de Python.

Les fonctions locals() et globals() retournent respectivement un dictionnaire des espaces de noms locaux et globaux.


variable_globale = "Je suis globale"

def fonction_externe():
    variable_externe = "Je suis externe"

    def fonction_interne():
        variable_interne = "Je suis interne"
        print("Dans fonction_interne:")
        print("  Accès à variable_interne:", variable_interne) # Local
        print("  Accès à variable_externe:", variable_externe) # Enclosing
        print("  Accès à variable_globale:", variable_globale) # Global
        # print("Accès à print:", print) # Built-in
        print("  Locals:", locals()) # Affiche {'variable_interne': 'Je suis interne'}

    fonction_interne()
    print("\nDans fonction_externe:")
    print("  Accès à variable_externe:", variable_externe) # Local à fonction_externe
    print("  Accès à variable_globale:", variable_globale) # Global
    print("  Locals:", locals()) # Affiche {'variable_externe': 'Je suis externe'}

print("Dans l'espace global:")
print("  Accès à variable_globale:", variable_globale) # Global
# print(variable_externe) # Ceci générerait une NameError
print("  Globals:", globals()) # Affiche le contenu de l'espace de noms global

fonction_externe()

global et nonlocal

global

Le mot-clé global permet de modifier une variable définie dans l'espace de noms global depuis l'intérieur d'une fonction locale.


compteur_global = 0

def incrementer_global():
    global compteur_global # Indique qu'on veut modifier la variable globale
    compteur_global += 1
    print("Dans incrementer_global:", compteur_global)

print("Avant appel:", compteur_global)
incrementer_global()
print("Après appel:", compteur_global)
# Output:
# Avant appel: 0
# Dans incrementer_global: 1
# Après appel: 1

# Utilisation de global pour introduire une nouvelle variable globale
def creer_variable_globale():
    global nouvelle_variable
    nouvelle_variable = "Créée globalement"
    print("Dans creer_variable_globale, nouvelle_variable est:", nouvelle_variable)

creer_variable_globale()
print("En dehors, nouvelle_variable est:", nouvelle_variable)
# Output:
# Dans creer_variable_globale, nouvelle_variable est: Créée globalement
# En dehors, nouvelle_variable est: Créée globalement

nonlocal

Le mot-clé nonlocal est utilisé dans les fonctions imbriquées pour modifier une variable qui n'est ni locale à la fonction courante, ni globale, mais qui appartient à l'espace de noms de la fonction englobante la plus proche.


def fonction_externe_nonlocal():
    variable_externe = "Valeur initiale externe"

    def fonction_interne_nonlocal():
        nonlocal variable_externe # Référence la variable de fonction_externe_nonlocal
        variable_externe = "Valeur modifiée par interne"
        print("Dans fonction_interne_nonlocal:", variable_externe)

    print("Avant appel interne:", variable_externe)
    fonction_interne_nonlocal()
    print("Après appel interne:", variable_externe)

fonction_externe_nonlocal()
# Output:
# Avant appel interne: Valeur initiale externe
# Dans fonction_interne_nonlocal: Valeur modifiée par interne
# Après appel interne: Valeur modifiée par interne

Si nonlocal n'est pas utilisé, une affectation à variable_externe dans fonction_interne_nonlocal créerait une nouvelle variable locale portant le même nom, sans affecter celle de fonction_externe_nonlocal.

Étiquettes: Python fonctions paramètres dynamiques args kwargs

Publié le 28 août à 20h04