Python

⭐ Maßtrise totale

Métaclasses

Les mĂ©taclasses permettent de personnaliser la crĂ©ation de classes elles-mĂȘmes.

class MetaSingleton(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super().__call__(*args, **kwargs) return cls._instances[cls] class Database(metaclass=MetaSingleton): def __init__(self): self.connected = False def connect(self): self.connected = True # Utilisation db1 = Database() db2 = Database() print(db1 is db2) # True - mĂȘme instance # MĂ©taclasse pour validation class ValidationMeta(type): def __new__(mcs, name, bases, attrs): # VĂ©rifier que toutes les mĂ©thodes ont des docstrings for key, value in attrs.items(): if callable(value) and not key.startswith('__'): if not value.__doc__: raise TypeError(f"La mĂ©thode {key} doit avoir une docstring") return super().__new__(mcs, name, bases, attrs) class MaClasse(metaclass=ValidationMeta): def ma_methode(self): """Cette mĂ©thode fait quelque chose""" pass

Descripteurs

class Validateur: def __init__(self, minimum=None, maximum=None): self.minimum = minimum self.maximum = maximum def __set_name__(self, owner, name): self.name = name def __get__(self, obj, objtype=None): if obj is None: return self return obj.__dict__.get(self.name) def __set__(self, obj, value): if self.minimum is not None and value < self.minimum: raise ValueError(f"{self.name} doit ĂȘtre >= {self.minimum}") if self.maximum is not None and value > self.maximum: raise ValueError(f"{self.name} doit ĂȘtre <= {self.maximum}") obj.__dict__[self.name] = value class Personne: age = Validateur(minimum=0, maximum=150) def __init__(self, nom, age): self.nom = nom self.age = age # Utilisation p = Personne("Alice", 25) print(p.age) # 25 # p.age = 200 # LĂšve ValueError

Context Variables (Python 3.7+)

import contextvars import asyncio # Variable de contexte request_id = contextvars.ContextVar('request_id', default=None) async def traiter_requete(id_requete): # DĂ©finir dans le contexte actuel request_id.set(id_requete) await asyncio.sleep(0.1) # RĂ©cupĂ©rer depuis le contexte print(f"Traitement de la requĂȘte {request_id.get()}") async def main(): # Chaque tĂąche a son propre contexte tasks = [ asyncio.create_task(traiter_requete(i)) for i in range(5) ] await asyncio.gather(*tasks) # asyncio.run(main())

Protocol et Structural Subtyping

from typing import Protocol, runtime_checkable @runtime_checkable class Drawable(Protocol): def draw(self) -> str: ... class Circle: def draw(self) -> str: return "Drawing circle" class Square: def draw(self) -> str: return "Drawing square" def render(shape: Drawable) -> None: print(shape.draw()) # Fonctionne mĂȘme sans hĂ©ritage explicite circle = Circle() square = Square() render(circle) # OK render(square) # OK print(isinstance(circle, Drawable)) # True

Optimisation de performance

Profiling

import cProfile import pstats from functools import lru_cache def fibonacci_lent(n): if n < 2: return n return fibonacci_lent(n-1) + fibonacci_lent(n-2) @lru_cache(maxsize=None) def fibonacci_rapide(n): if n < 2: return n return fibonacci_rapide(n-1) + fibonacci_rapide(n-2) # Profiler profiler = cProfile.Profile() profiler.enable() fibonacci_rapide(30) profiler.disable() stats = pstats.Stats(profiler) stats.sort_stats('cumulative') stats.print_stats(10) # Avec timeit import timeit temps = timeit.timeit( 'fibonacci_rapide(30)', setup='from __main__ import fibonacci_rapide', number=1000 ) print(f"Temps: {temps}s")

Slots pour économiser la mémoire

class PersonneNormale: def __init__(self, nom, age): self.nom = nom self.age = age class PersonneOptimisee: __slots__ = ['nom', 'age'] def __init__(self, nom, age): self.nom = nom self.age = age # PersonneOptimisee utilise moins de mémoire # et les accÚs aux attributs sont plus rapides import sys p1 = PersonneNormale("Alice", 25) p2 = PersonneOptimisee("Bob", 30) print(sys.getsizeof(p1.__dict__)) # Plus grand print(sys.getsizeof(p2)) # Plus petit

Générateurs pour l'efficacité mémoire

# Mauvais: charge tout en mémoire def lire_gros_fichier_mal(filename): with open(filename) as f: return f.readlines() # Bon: traite ligne par ligne def lire_gros_fichier(filename): with open(filename) as f: for ligne in f: yield ligne.strip() # Pipeline de générateurs def filtrer_lignes(lignes): for ligne in lignes: if ligne: yield ligne def transformer_lignes(lignes): for ligne in lignes: yield ligne.upper() # Composition efficace # pipeline = transformer_lignes( # filtrer_lignes( # lire_gros_fichier('gros_fichier.txt') # ) # )

Abstract Base Classes

from abc import ABC, abstractmethod from typing import List class BaseDeDonnees(ABC): @abstractmethod def connecter(self) -> None: pass @abstractmethod def executer(self, requete: str) -> List: pass @abstractmethod def fermer(self) -> None: pass class MySQL(BaseDeDonnees): def connecter(self): print("Connexion à MySQL") def executer(self, requete): print(f"Exécution: {requete}") return [] def fermer(self): print("Fermeture MySQL") # Impossible d'instancier BaseDeDonnees directement # db = BaseDeDonnees() # TypeError db = MySQL() db.connecter()

Injection de dépendances

from typing import Protocol from dataclasses import dataclass class Logger(Protocol): def log(self, message: str) -> None: ... class ConsoleLogger: def log(self, message: str) -> None: print(f"[LOG] {message}") class FileLogger: def __init__(self, filename: str): self.filename = filename def log(self, message: str) -> None: with open(self.filename, 'a') as f: f.write(f"{message}\n") @dataclass class UserService: logger: Logger def create_user(self, username: str) -> None: # Logique métier self.logger.log(f"Création de l'utilisateur {username}") # Injection console_logger = ConsoleLogger() service = UserService(logger=console_logger) service.create_user("alice") # Facile de changer l'implémentation file_logger = FileLogger("app.log") service2 = UserService(logger=file_logger) service2.create_user("bob")

Design Patterns avancés

Factory Pattern avec ABCs

from abc import ABC, abstractmethod from enum import Enum class VehiculeType(Enum): VOITURE = "voiture" MOTO = "moto" class Vehicule(ABC): @abstractmethod def demarrer(self) -> str: pass class Voiture(Vehicule): def demarrer(self) -> str: return "La voiture démarre" class Moto(Vehicule): def demarrer(self) -> str: return "La moto démarre" class VehiculeFactory: _vehicules = { VehiculeType.VOITURE: Voiture, VehiculeType.MOTO: Moto } @classmethod def creer(cls, type_vehicule: VehiculeType) -> Vehicule: vehicule_class = cls._vehicules.get(type_vehicule) if not vehicule_class: raise ValueError(f"Type de véhicule inconnu: {type_vehicule}") return vehicule_class() # Utilisation voiture = VehiculeFactory.creer(VehiculeType.VOITURE) print(voiture.demarrer())

Strategy Pattern

from typing import Protocol class StrategieCompression(Protocol): def compresser(self, data: str) -> str: ... class CompressionZip: def compresser(self, data: str) -> str: return f"[ZIP] {data}" class CompressionGzip: def compresser(self, data: str) -> str: return f"[GZIP] {data}" class Fichier: def __init__(self, strategie: StrategieCompression): self.strategie = strategie def sauvegarder(self, data: str) -> None: data_compresse = self.strategie.compresser(data) print(f"Sauvegarde: {data_compresse}") # Utilisation fichier = Fichier(CompressionZip()) fichier.sauvegarder("mes données") # Changer de stratégie dynamiquement fichier.strategie = CompressionGzip() fichier.sauvegarder("autres données")

Manipulation de bytecode

import dis def exemple(): x = 1 y = 2 return x + y # Afficher le bytecode dis.dis(exemple) # Modification dynamique de code def creer_fonction_dynamique(operation): code = f""" def fonction_generee(a, b): return a {operation} b """ namespace = {} exec(code, namespace) return namespace['fonction_generee'] # Créer des fonctions dynamiquement addition = creer_fonction_dynamique('+') multiplication = creer_fonction_dynamique('*') print(addition(5, 3)) # 8 print(multiplication(5, 3)) # 15

Memory Management avancé

import weakref import gc class ObjetLourd: def __init__(self, nom): self.nom = nom self.data = [0] * 1000000 def __del__(self): print(f"{self.nom} est détruit") # WeakRef pour éviter les références circulaires class Noeud: def __init__(self, valeur): self.valeur = valeur self.enfants = [] self.parent = None def ajouter_enfant(self, enfant): self.enfants.append(enfant) enfant.parent = weakref.ref(self) # Référence faible # Gestion manuelle du garbage collector gc.disable() # Désactiver GC auto # Votre code intensif... gc.collect() # Forcer une collection gc.enable() # Réactiver # Monitoring de mémoire import tracemalloc tracemalloc.start() # Code à profiler objets = [ObjetLourd(f"obj{i}") for i in range(10)] snapshot = tracemalloc.take_snapshot() top_stats = snapshot.statistics('lineno') for stat in top_stats[:5]: print(stat)

Projets avancés suggérés

  1. Créer un ORM complet avec métaclasses et descripteurs
  2. Implémenter un framework web asynchrone avec ASGI
  3. Développer un compilateur JIT pour un sous-ensemble de Python
  4. Créer un systÚme de plugins avec chargement dynamique
  5. Implémenter un garbage collector concurrent
  6. Développer une bibliothÚque de parsing avec PEG