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"""
passclass 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 ValueErrorimport 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())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)) # Trueimport 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")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# 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')
# )
# )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()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")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())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")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)) # 15import 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)