Fonctions et décorateurs : application des fermetures et implémentation d'un panier d'achat basique

Concepts fondamentaux

Les fonctions imbriquées et les fermetures constituent la base des décorateurs en Python. Une fermeture est une fonction définie à l'intérieur d'une autre fonction qui référence des variables de la portée externe.

Exemple de fermeture simple

def fonction_externe():
    valeur = 42
    
    def fonction_interne():
        print(f"Valeur capturée : {valeur}")
    
    return fonction_interne

# Création de la fermeture
fermeture = fonction_externe()
fermeture()  # Affiche : Valeur capturée : 42

Transmission de paramètres via fermeture

def create_comparateur(a, b):
    def comparer():
        return max(a, b)
    return comparer

comparaison = create_comparateur(15, 8)
resultat = comparaison()
print(resultat)  # Affiche : 15

Utilisation avec le module requests

Application pratique des fermetures pour encapsuler des requêtes HTTP :

import requests

def create_fetcher(url_cible):
    def fetch_data():
        try:
            reponse = requests.get(url_cible)
            if reponse.status_code == 200:
                print(f"Longueur du contenu : {len(reponse.text)}")
                return reponse.text
        except Exception as e:
            print(f"Erreur : {e}")
    return fetch_data

# Création de fonctions spécialisées
fetch_baidu = create_fetcher("https://www.baidu.com")
contenu = fetch_baidu()

Introduction aux décorateurs

Un décorateur est un outil permettant d'ajouter des fonctionnalités à une fonction existante sans modifier son code source.

Décorateur basique pour mesurer le temps d'exécution

import time

def mesure_temps(func):
    def wrapper(*args, **kwargs):
        debut = time.time()
        resultat = func(*args, **kwargs)
        fin = time.time()
        print(f"Temps d'exécution : {fin - debut:.4f} secondes")
        return resultat
    return wrapper

@mesure_temps
def traitement_long():
    time.sleep(2)
    print("Traitement terminé")

traitement_long()

Syntaxe sucre des décorateurs

# Équivalent à : fonction_originale = decorateur(fonction_originale)
@decorateur
def fonction_originale():
    pass

Implémentation du panier d'achat

Structure complète d'un système d'achat avec gestion utilisateur :

import os

# Données utilisateur
etat_utilisateur = {
    'nom': None,
    'mot_de_passe': None,
    'solde': None
}

# Catalogue produits
catalogue = [
    ['Vélo électrique', 1200],
    ['Lait bio', 3.50],
    ['Chaussures sport', 89.90],
    ['Jambon blanc', 4.20]
]

# Panier d'achat
panier = {}

def inscription():
    while True:
        nom = input("Nom utilisateur : ").strip()
        if os.path.exists(f"{nom}.txt"):
            print("Utilisateur déjà existant")
            continue
        
        mot_de_passe = input("Mot de passe : ").strip()
        confirmation = input("Confirmer mot de passe : ").strip()
        
        if mot_de_passe == confirmation:
            donnees = f"{nom},{mot_de_passe},1000"
            with open(f"{nom}.txt", 'w', encoding='utf-8') as f:
                f.write(donnees)
            print("Inscription réussie !")
            break
        else:
            print("Mots de passe différents")

from functools import wraps

def authentification_requise(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        if etat_utilisateur['nom']:
            return func(*args, **kwargs)
        else:
            print("Authentification nécessaire")
            connexion()
            return func(*args, **kwargs)
    return wrapper

def connexion():
    while True:
        nom = input("Nom : ").strip()
        if not os.path.exists(f"{nom}.txt"):
            print("Utilisateur inexistant")
            continue
            
        with open(f"{nom}.txt", 'r', encoding='utf-8') as f:
            donnees = f.read().strip()
            nom_fichier, mdp_fichier, solde_fichier = donnees.split(',')
            
            if nom == nom_fichier:
                mot_de_passe = input("Mot de passe : ").strip()
                if mot_de_passe == mdp_fichier:
                    etat_utilisateur.update({
                        'nom': nom,
                        'mot_de_passe': mot_de_passe,
                        'solde': float(solde_fichier)
                    })
                    print("Connexion réussie")
                    break
                else:
                    print("Mot de passe incorrect")
            else:
                print("Nom d'utilisateur incorrect")

@authentification_requise
def magasiner():
    total_achats = 0
    
    while True:
        print("\n=== Magasin ===")
        for idx, (produit, prix) in enumerate(catalogue, 1):
            print(f"{idx}. {produit} - {prix}€")
        
        choix = input("Choisissez un produit (q pour quitter) : ").strip()
        
        if choix == 'q':
            if total_achats > 0 and input("Finaliser les achats ? (o/n) : ") == 'o':
                etat_utilisateur['solde'] -= total_achats
                with open(f"{etat_utilisateur['nom']}.txt", 'w') as f:
                    nouvelles_donnees = f"{etat_utilisateur['nom']},{etat_utilisateur['mot_de_passe']},{etat_utilisateur['solde']}"
                    f.write(nouvelles_donnees)
                print("Achats effectués !")
            break
            
        if choix.isdigit():
            index = int(choix) - 1
            if 0 <= index < len(catalogue):
                produit, prix = catalogue[index]
                if etat_utilisateur['solde'] >= prix:
                    panier[produit] = panier.get(produit, 0) + 1
                    total_achats += prix
                    print(f"Ajouté au panier : {produit}")
                else:
                    print("Solde insuffisant")
            else:
                print("Choix invalide")

# Mapping des fonctionnalités
fonctions = {
    '1': inscription,
    '2': connexion,
    '3': magasiner
}

def interface_principale():
    print("Bienvenue dans notre boutique !")
    while True:
        print("\n1. S'inscrire\n2. Se connecter\n3. Magasiner\nq. Quitter")
        choix = input("Votre choix : ").strip()
        
        if choix == 'q':
            print("Au revoir !")
            break
        elif choix in fonctions:
            fonctions[choix]()
        else:
            print("Option invalide")

if __name__ == "__main__":
    interface_principale()

Techniques avancées

Utilisation de wraps pour préserver les métadonnnées

from functools import wraps

def decorateur_tracage(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Appel de {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@decorateur_tracage
def exemple_fonction():
    """Documentation de la fonction"""
    return "Résultat"

print(exemple_fonction.__name__)  # exemple_fonction
print(exemple_fonction.__doc__)   # Documentation de la fonction

Décorateurs multiples

def decorateur_a(func):
    def wrapper(*args, **kwargs):
        print("Décorateur A - Entrée")
        resultat = func(*args, **kwargs)
        print("Décorateur A - Sortie")
        return resultat
    return wrapper

def decorateur_b(func):
    def wrapper(*args, **kwargs):
        print("Décorateur B - Entrée")
        resultat = func(*args, **kwargs)
        print("Décorateur B - Sortie")
        return resultat
    return wrapper

@decorateur_a
@decorateur_b
def fonction_test():
    print("Exécution principale")

fonction_test()
# Ordre d'exécution :
# Décorateur A - Entrée
# Décorateur B - Entrée
# Exécution principale
# Décorateur B - Sortie
# Décorateur A - Sortie

Étiquettes: Python Decorators closures shopping-cart requests

Publié le 22 septembre à 02h09