Python

🚀 Intermédiaire

Décorateurs

Les décorateurs permettent de modifier le comportement d'une fonction sans changer son code.

Décorateur simple

def mon_decorateur(func): def wrapper(): print("Avant la fonction") func() print("Après la fonction") return wrapper @mon_decorateur def dire_bonjour(): print("Bonjour!") dire_bonjour() # Sortie: # Avant la fonction # Bonjour! # Après la fonction

Décorateur avec arguments

def decorateur_avec_args(func): def wrapper(*args, **kwargs): print(f"Arguments: {args}, {kwargs}") resultat = func(*args, **kwargs) print(f"Résultat: {resultat}") return resultat return wrapper @decorateur_avec_args def additionner(a, b): return a + b additionner(5, 3)

Décorateur avec paramètres

def repeter(n): def decorateur(func): def wrapper(*args, **kwargs): for _ in range(n): func(*args, **kwargs) return wrapper return decorateur @repeter(3) def saluer(nom): print(f"Bonjour {nom}!") saluer("Alice") # Affiche 3 fois

Décorateur pratique : Timer

import time from functools import wraps def timer(func): @wraps(func) def wrapper(*args, **kwargs): debut = time.time() resultat = func(*args, **kwargs) fin = time.time() print(f"{func.__name__} a pris {fin - debut:.4f} secondes") return resultat return wrapper @timer def calcul_lent(): time.sleep(1) return sum(range(1000000)) calcul_lent()

Context Managers

Avec la classe

class GestionFichier: def __init__(self, nom_fichier, mode): self.nom_fichier = nom_fichier self.mode = mode self.fichier = None def __enter__(self): self.fichier = open(self.nom_fichier, self.mode) return self.fichier def __exit__(self, exc_type, exc_val, exc_tb): if self.fichier: self.fichier.close() return False # Utilisation with GestionFichier("test.txt", "w") as f: f.write("Hello World!") # Le fichier est automatiquement fermé

Avec contextlib

from contextlib import contextmanager @contextmanager def timer_context(): import time debut = time.time() yield fin = time.time() print(f"Temps écoulé: {fin - debut:.4f}s") # Utilisation with timer_context(): # Code à chronométrer sum(range(1000000))

Programmation fonctionnelle

Map, Filter, Reduce

from functools import reduce # Map: appliquer une fonction à chaque élément nombres = [1, 2, 3, 4, 5] doubles = list(map(lambda x: x * 2, nombres)) print(doubles) # [2, 4, 6, 8, 10] # Filter: filtrer selon une condition pairs = list(filter(lambda x: x % 2 == 0, nombres)) print(pairs) # [2, 4] # Reduce: réduire à une seule valeur somme = reduce(lambda x, y: x + y, nombres) print(somme) # 15 # Combinaison resultat = reduce( lambda x, y: x + y, map(lambda x: x ** 2, filter(lambda x: x % 2 == 0, range(10))) ) print(resultat) # 120 (0² + 2² + 4² + 6² + 8²)

Fonctions d'ordre supérieur

# Fonction qui retourne une fonction def creer_multiplicateur(n): def multiplicateur(x): return x * n return multiplicateur fois_deux = creer_multiplicateur(2) fois_trois = creer_multiplicateur(3) print(fois_deux(5)) # 10 print(fois_trois(5)) # 15 # Fonction qui prend une fonction en argument def appliquer_operation(operation, liste): return [operation(x) for x in liste] nombres = [1, 2, 3, 4, 5] carres = appliquer_operation(lambda x: x**2, nombres) print(carres) # [1, 4, 9, 16, 25]

Expressions régulières

import re texte = "Mon email est alice@example.com et mon numéro est 06-12-34-56-78" # Chercher un pattern email = re.search(r'[\w\.-]+@[\w\.-]+', texte) if email: print(f"Email trouvé: {email.group()}") # Trouver toutes les occurrences mots = re.findall(r'\b\w+\b', texte) print(f"Mots: {mots}") # Remplacer cache = re.sub(r'\d{2}-\d{2}-\d{2}-\d{2}-\d{2}', 'XX-XX-XX-XX-XX', texte) print(cache) # Groupes de capture pattern = r'(\d{2})-(\d{2})-(\d{2})-(\d{2})-(\d{2})' match = re.search(pattern, texte) if match: print(f"Numéro complet: {match.group(0)}") print(f"Premier groupe: {match.group(1)}") # Compilation pour performances pattern_compile = re.compile(r'\d+') nombres = pattern_compile.findall("Il y a 123 pommes et 456 oranges") print(nombres) # ['123', '456']

Multithreading et Multiprocessing

Threading

import threading import time def tache(nom, duree): print(f"{nom} démarre") time.sleep(duree) print(f"{nom} terminé") # Créer des threads threads = [] for i in range(5): t = threading.Thread(target=tache, args=(f"Thread-{i}", 1)) threads.append(t) t.start() # Attendre tous les threads for t in threads: t.join() print("Tous les threads sont terminés")

Thread avec classe

import threading class MonThread(threading.Thread): def __init__(self, nom): super().__init__() self.nom = nom def run(self): print(f"{self.nom} démarre") time.sleep(1) print(f"{self.nom} terminé") # Utilisation threads = [MonThread(f"Thread-{i}") for i in range(3)] for t in threads: t.start() for t in threads: t.join()

Thread Lock

import threading compteur = 0 lock = threading.Lock() def incrementer(): global compteur for _ in range(100000): with lock: compteur += 1 threads = [threading.Thread(target=incrementer) for _ in range(10)] for t in threads: t.start() for t in threads: t.join() print(f"Compteur: {compteur}")

Multiprocessing

from multiprocessing import Process, Pool import os def travail(nom): print(f"Process {nom} - PID: {os.getpid()}") return nom * 2 # Avec Process if __name__ == '__main__': processes = [] for i in range(5): p = Process(target=travail, args=(i,)) processes.append(p) p.start() for p in processes: p.join() # Avec Pool with Pool(4) as pool: resultats = pool.map(travail, range(10)) print(resultats)

Type Hints et Annotations

from typing import List, Dict, Tuple, Optional, Union def saluer(nom: str) -> str: return f"Bonjour {nom}" def additionner(a: int, b: int) -> int: return a + b # Collections def traiter_liste(nombres: List[int]) -> int: return sum(nombres) def get_info() -> Tuple[str, int]: return "Alice", 25 # Optional (peut être None) def chercher(nom: str) -> Optional[Dict[str, str]]: database = {"Alice": "alice@example.com"} return database.get(nom) # Union (plusieurs types possibles) def traiter(valeur: Union[int, str]) -> str: if isinstance(valeur, int): return str(valeur) return valeur.upper() # Type alias Coordonnees = Tuple[float, float] def distance(point: Coordonnees) -> float: x, y = point return (x**2 + y**2) ** 0.5

Dataclasses

from dataclasses import dataclass, field from typing import List @dataclass class Personne: nom: str age: int email: str = "non@fourni.com" # Valeur par défaut amis: List[str] = field(default_factory=list) def anniversaire(self): self.age += 1 # Utilisation alice = Personne("Alice", 25) print(alice) # Personne(nom='Alice', age=25, email='non@fourni.com', amis=[]) alice.amis.append("Bob") alice.anniversaire() print(alice.age) # 26 # Avec post_init @dataclass class Rectangle: largeur: float hauteur: float aire: float = field(init=False) def __post_init__(self): self.aire = self.largeur * self.hauteur rect = Rectangle(10, 20) print(rect.aire) # 200

Async/Await

import asyncio import aiohttp # Fonction asynchrone simple async def dire_apres(delai, message): await asyncio.sleep(delai) print(message) return message # Exécuter plusieurs coroutines async def main(): tache1 = asyncio.create_task(dire_apres(1, "Bonjour")) tache2 = asyncio.create_task(dire_apres(2, "Au revoir")) resultat1 = await tache1 resultat2 = await tache2 # Lancer asyncio.run(main()) # Requêtes HTTP asynchrones async def fetch(url): async with aiohttp.ClientSession() as session: async with session.get(url) as response: return await response.text() async def fetch_multiple(): urls = [ 'https://api.example.com/1', 'https://api.example.com/2', 'https://api.example.com/3' ] tasks = [fetch(url) for url in urls] resultats = await asyncio.gather(*tasks) return resultats # asyncio.run(fetch_multiple())

Exercices pratiques

  1. Créez un décorateur de cache pour mémoriser les résultats de fonctions
  2. Implémentez un web scraper asynchrone avec asyncio et aiohttp
  3. Développez un système de logs thread-safe avec décorateurs
  4. Créez une API REST simple avec type hints complets
  5. Implémentez un pattern Observer avec décorateurs