Introduction
Python est l’un des langages de programmation les plus accessibles et puissants pour débuter dans le développement. Sa syntaxe claire et ses structures de données intégrées en font un choix idéal pour apprendre la programmation tout en construisant des applications réelles.
Dans cet article, vous découvrirez les fondamentaux de Python 3.10+ avec des exemples concrets, des bonnes pratiques, et des projets pratiques pour consolider vos connaissances.
Configuration de l’environnement virtuel
Avant de commencer, créons un environnement de développement isolé et professionnel.
Installation et création de l’environnement
# Vérifier la version de Python (3.10 ou supérieur requis)
python3 --version
# Créer un environnement virtuel
python3 -m venv venv
# Activer l'environnement virtuel
# Sur Linux/Mac:
source venv/bin/activate
# Sur Windows:
# venvScriptsactivate
# Mettre à jour pip
pip install --upgrade pip
Fichier requirements.txt
# requirements.txt - Dépendances pour le développement
pytest==7.4.3
pytest-cov==4.1.0
black==23.12.1
mypy==1.7.1
pylint==3.0.3
ipython==8.18.1
Installation des dépendances:
pip install -r requirements.txt
Syntaxe de base et types de données
Variables et types primitifs
"""
basic_types.py - Démonstration des types de base en Python
"""
from typing import Final
# Constantes (convention: MAJUSCULES)
MAX_USERS: Final[int] = 100
PI: Final[float] = 3.14159
APP_NAME: Final[str] = "MonApplication"
# Variables avec type hints
age: int = 25
prix: float = 19.99
nom: str = "Alice"
est_actif: bool = True
# Inférence de type (Python devine le type)
ville = "Paris" # Python sait que c'est une str
compteur = 0 # Python sait que c'est un int
def afficher_informations(nom: str, age: int, ville: str) -> None:
"""
Affiche les informations d'un utilisateur.
Args:
nom: Le nom de l'utilisateur
age: L'âge de l'utilisateur
ville: La ville de résidence
"""
print(f"Nom: {nom}")
print(f"Age: {age} ans")
print(f"Ville: {ville}")
print(f"Application: {APP_NAME}")
if __name__ == "__main__":
afficher_informations(nom, age, ville)
Opérateurs et expressions
"""
operators.py - Opérateurs arithmétiques, logiques et de comparaison
"""
from typing import Union
def calculatrice_basique(
a: Union[int, float],
b: Union[int, float],
operation: str
) -> Union[int, float]:
"""
Effectue une opération arithmétique de base.
Args:
a: Premier nombre
b: Deuxième nombre
operation: Type d'opération (+, -, *, /, //, %, **)
Returns:
Le résultat de l'opération
Raises:
ValueError: Si l'opération n'est pas reconnue
ZeroDivisionError: Si division par zéro
"""
operations = {
"+": a + b, # Addition
"-": a - b, # Soustraction
"*": a * b, # Multiplication
"/": a / b, # Division
"//": a // b, # Division entière
"%": a % b, # Modulo (reste)
"**": a ** b, # Puissance
}
if operation not in operations:
raise ValueError(f"Opération '{operation}' non reconnue")
return operations[operation]
# Opérateurs de comparaison
def est_dans_intervalle(valeur: float, min_val: float, max_val: float) -> bool:
"""Vérifie si une valeur est dans un intervalle."""
return min_val <= valeur <= max_val
# Opérateurs logiques
def peut_voter(age: int, nationalite: str, casier_judiciaire: bool) -> bool:
"""
Détermine si une personne peut voter.
Args:
age: L'âge de la personne
nationalite: La nationalité
casier_judiciaire: True si casier judiciaire
Returns:
True si la personne peut voter
"""
return (
age >= 18
and nationalite == "française"
and not casier_judiciaire
)
if __name__ == "__main__":
# Tests des opérateurs
print(f"10 + 5 = {calculatrice_basique(10, 5, '+')}")
print(f"10 / 3 = {calculatrice_basique(10, 3, '/')}")
print(f"10 // 3 = {calculatrice_basique(10, 3, '//')}")
print(f"10 % 3 = {calculatrice_basique(10, 3, '%')}")
print(f"2 ** 8 = {calculatrice_basique(2, 8, '**')}")
print(f"n15 est entre 10 et 20: {est_dans_intervalle(15, 10, 20)}")
print(f"Peut voter (25 ans, française, pas de casier): {peut_voter(25, 'française', False)}")
Structures de données intégrées
Listes (Lists)
"""
lists_operations.py - Opérations avancées sur les listes
"""
from typing import List, Optional, Any
def gestion_taches() -> None:
"""Démonstration complète des opérations sur les listes."""
# Création de listes
taches: List[str] = ["Apprendre Python", "Faire les courses", "Répondre aux emails"]
nombres: List[int] = [1, 2, 3, 4, 5]
mixte: List[Any] = [1, "texte", 3.14, True] # Type mixte (éviter en production)
# Ajout d'éléments
taches.append("Appeler le client") # Ajoute à la fin
taches.insert(0, "Vérifier les emails") # Insère à l'index 0
taches.extend(["Réunion d'équipe", "Code review"]) # Ajoute plusieurs éléments
# Suppression d'éléments
taches.remove("Faire les courses") # Supprime par valeur
element_supprime = taches.pop() # Supprime et retourne le dernier élément
del taches[0] # Supprime par index
# Accès et modification
premiere_tache = taches[0]
derniere_tache = taches[-1]
taches[1] = "Nouvelle tâche"
# Slicing (découpage)
deux_premieres = taches[:2]
deux_dernieres = taches[-2:]
milieu = taches[1:3]
# List comprehension (compréhension de liste)
nombres_carres = [n ** 2 for n in nombres]
nombres_pairs = [n for n in nombres if n % 2 == 0]
print("Tâches actuelles:", taches)
print("Carrés:", nombres_carres)
print("Nombres pairs:", nombres_pairs)
def filtrer_et_transformer(
nombres: List[int],
seuil: int
) -> List[int]:
"""
Filtre et transforme une liste de nombres.
Args:
nombres: Liste de nombres à traiter
seuil: Valeur minimale pour le filtre
Returns:
Liste des nombres supérieurs au seuil, multipliés par 2
"""
return [n * 2 for n in nombres if n > seuil]
def trouver_element(
liste: List[Any],
element: Any
) -> Optional[int]:
"""
Trouve l'index d'un élément dans une liste.
Args:
liste: La liste à parcourir
element: L'élément à chercher
Returns:
L'index de l'élément ou None si non trouvé
"""
try:
return liste.index(element)
except ValueError:
return None
if __name__ == "__main__":
gestion_taches()
nombres = [1, 5, 10, 15, 20, 25]
resultat = filtrer_et_transformer(nombres, 10)
print(f"nNombres > 10, multipliés par 2: {resultat}")
index = trouver_element(nombres, 15)
print(f"Index de 15: {index}")
Dictionnaires (Dictionaries)
"""
dictionaries_operations.py - Gestion avancée des dictionnaires
"""
from typing import Dict, List, Optional, Any
def gestion_utilisateurs() -> None:
"""Démonstration des opérations sur les dictionnaires."""
# Création de dictionnaires
utilisateur: Dict[str, Any] = {
"id": 1,
"nom": "Dupont",
"prenom": "Marie",
"age": 28,
"email": "marie.dupont@example.com",
"roles": ["user", "moderator"]
}
# Accès aux valeurs
nom = utilisateur["nom"]
age = utilisateur.get("age") # Plus sûr (retourne None si absent)
telephone = utilisateur.get("telephone", "Non renseigné") # Valeur par défaut
# Modification et ajout
utilisateur["age"] = 29
utilisateur["ville"] = "Paris"
# Suppression
del utilisateur["roles"]
email_supprime = utilisateur.pop("email")
# Itération
for cle, valeur in utilisateur.items():
print(f"{cle}: {valeur}")
# Clés et valeurs
toutes_les_cles = list(utilisateur.keys())
toutes_les_valeurs = list(utilisateur.values())
print(f"nClés: {toutes_les_cles}")
print(f"Valeurs: {toutes_les_valeurs}")
class GestionnaireUtilisateurs:
"""Gestionnaire d'utilisateurs avec dictionnaire."""
def __init__(self) -> None:
"""Initialise le gestionnaire."""
self.utilisateurs: Dict[int, Dict[str, Any]] = {}
self._prochain_id: int = 1
def ajouter_utilisateur(
self,
nom: str,
prenom: str,
email: str,
age: int
) -> int:
"""
Ajoute un nouvel utilisateur.
Args:
nom: Nom de famille
prenom: Prénom
email: Adresse email
age: Age
Returns:
L'ID de l'utilisateur créé
"""
utilisateur_id = self._prochain_id
self.utilisateurs[utilisateur_id] = {
"nom": nom,
"prenom": prenom,
"email": email,
"age": age
}
self._prochain_id += 1
return utilisateur_id
def obtenir_utilisateur(self, user_id: int) -> Optional[Dict[str, Any]]:
"""Récupère un utilisateur par son ID."""
return self.utilisateurs.get(user_id)
def rechercher_par_age(self, age_min: int, age_max: int) -> List[Dict[str, Any]]:
"""
Recherche des utilisateurs par tranche d'âge.
Args:
age_min: Age minimum
age_max: Age maximum
Returns:
Liste des utilisateurs correspondants
"""
return [
{**user, "id": uid}
for uid, user in self.utilisateurs.items()
if age_min <= user["age"] <= age_max
]
def statistiques(self) -> Dict[str, Any]:
"""Calcule des statistiques sur les utilisateurs."""
if not self.utilisateurs:
return {"total": 0, "age_moyen": 0}
ages = [user["age"] for user in self.utilisateurs.values()]
return {
"total": len(self.utilisateurs),
"age_moyen": sum(ages) / len(ages),
"age_min": min(ages),
"age_max": max(ages)
}
if __name__ == "__main__":
gestion_utilisateurs()
print("n" + "="*50)
print("Gestionnaire d'utilisateurs")
print("="*50 + "n")
gestionnaire = GestionnaireUtilisateurs()
# Ajout d'utilisateurs
id1 = gestionnaire.ajouter_utilisateur("Dupont", "Marie", "marie@example.com", 28)
id2 = gestionnaire.ajouter_utilisateur("Martin", "Pierre", "pierre@example.com", 35)
id3 = gestionnaire.ajouter_utilisateur("Durand", "Sophie", "sophie@example.com", 22)
# Recherche
jeunes = gestionnaire.rechercher_par_age(20, 30)
print(f"Utilisateurs entre 20 et 30 ans: {jeunes}")
# Statistiques
stats = gestionnaire.statistiques()
print(f"nStatistiques: {stats}")
Tuples et Sets
"""
tuples_sets.py - Tuples et ensembles
"""
from typing import Tuple, Set, FrozenSet, List
# TUPLES - Immuables, ordonnés
def coordonnees_gps() -> Tuple[float, float]:
"""
Retourne des coordonnées GPS (latitude, longitude).
Returns:
Un tuple de coordonnées
"""
return 48.8566, 2.3522 # Paris
def informations_personne() -> Tuple[str, int, str]:
"""Retourne des informations immuables sur une personne."""
return "Dupont", 28, "Paris"
# Named tuples pour plus de lisibilité
from typing import NamedTuple
class Coordonnees(NamedTuple):
"""Coordonnées GPS avec noms explicites."""
latitude: float
longitude: float
altitude: float = 0.0 # Valeur par défaut
class Personne(NamedTuple):
"""Informations d'une personne."""
nom: str
age: int
ville: str
email: str = ""
# SETS - Ensembles sans doublons
class GestionEnsembles:
"""Gestion d'ensembles pour opérations mathématiques."""
@staticmethod
def supprimer_doublons(elements: List[str]) -> List[str]:
"""
Supprime les doublons d'une liste en préservant l'ordre.
Args:
elements: Liste avec potentiellement des doublons
Returns:
Liste sans doublons
"""
# Méthode 1: avec set (ne préserve pas l'ordre)
# return list(set(elements))
# Méthode 2: préserve l'ordre
vu: Set[str] = set()
resultat: List[str] = []
for element in elements:
if element not in vu:
vu.add(element)
resultat.append(element)
return resultat
@staticmethod
def operations_ensembles() -> None:
"""Démonstration des opérations sur les ensembles."""
# Création
fruits1: Set[str] = {"pomme", "banane", "orange"}
fruits2: Set[str] = {"banane", "kiwi", "mangue"}
# Union (tous les éléments)
tous_fruits = fruits1 | fruits2
# ou: tous_fruits = fruits1.union(fruits2)
# Intersection (éléments communs)
fruits_communs = fruits1 & fruits2
# ou: fruits_communs = fruits1.intersection(fruits2)
# Différence (dans fruits1 mais pas dans fruits2)
uniquement_fruits1 = fruits1 - fruits2
# ou: uniquement_fruits1 = fruits1.difference(fruits2)
# Différence symétrique (dans l'un ou l'autre, mais pas les deux)
fruits_differents = fruits1 ^ fruits2
# ou: fruits_differents = fruits1.symmetric_difference(fruits2)
print("Fruits1:", fruits1)
print("Fruits2:", fruits2)
print("Union:", tous_fruits)
print("Intersection:", fruits_communs)
print("Différence:", uniquement_fruits1)
print("Différence symétrique:", fruits_differents)
@staticmethod
def tags_articles() -> None:
"""Exemple pratique: gestion de tags d'articles."""
article1_tags: Set[str] = {"python", "programmation", "debutant"}
article2_tags: Set[str] = {"python", "web", "django"}
article3_tags: Set[str] = {"javascript", "web", "react"}
# Trouver les tags communs à tous les articles Python
tags_python = article1_tags & article2_tags
print(f"nTags communs aux articles Python: {tags_python}")
# Tous les tags uniques
tous_tags = article1_tags | article2_tags | article3_tags
print(f"Tous les tags: {tous_tags}")
# Tags spécifiques à Python
tags_specifiques_python = (article1_tags | article2_tags) - article3_tags
print(f"Tags spécifiques Python: {tags_specifiques_python}")
# FrozenSet - Ensemble immuable (peut être clé de dictionnaire)
def creer_permissions() -> FrozenSet[str]:
"""Crée un ensemble immuable de permissions."""
return frozenset(["lire", "ecrire", "executer"])
if __name__ == "__main__":
# Tuples
lat, lon = coordonnees_gps()
print(f"Coordonnées Paris: {lat}, {lon}")
# Named tuples
paris = Coordonnees(48.8566, 2.3522, 35.0)
print(f"Paris - Latitude: {paris.latitude}, Longitude: {paris.longitude}")
personne = Personne("Dupont", 28, "Paris", "dupont@example.com")
print(f"Personne: {personne.nom}, {personne.age} ans")
# Sets
print("n" + "="*50)
liste_avec_doublons = ["a", "b", "c", "a", "b", "d"]
sans_doublons = GestionEnsembles.supprimer_doublons(liste_avec_doublons)
print(f"Liste originale: {liste_avec_doublons}")
print(f"Sans doublons: {sans_doublons}")
print("n" + "="*50)
GestionEnsembles.operations_ensembles()
GestionEnsembles.tags_articles()
# FrozenSet
perms = creer_permissions()
print(f"nPermissions: {perms}")
Structures de contrôle
Conditions et boucles
"""
control_structures.py - Structures de contrôle avancées
"""
from typing import List, Dict, Optional, Any
from enum import Enum
class NiveauUtilisateur(Enum):
"""Énumération des niveaux d'utilisateur."""
INVITE = "invite"
UTILISATEUR = "utilisateur"
MODERATEUR = "moderateur"
ADMINISTRATEUR = "administrateur"
def verifier_acces(
niveau: NiveauUtilisateur,
age: int,
est_verifie: bool
) -> str:
"""
Vérifie les droits d'accès d'un utilisateur.
Args:
niveau: Niveau de l'utilisateur
age: Age de l'utilisateur
est_verifie: Si le compte est vérifié
Returns:
Message de statut d'accès
"""
# Conditions multiples avec elif
if niveau == NiveauUtilisateur.ADMINISTRATEUR:
return "Accès complet accordé"
elif niveau == NiveauUtilisateur.MODERATEUR and est_verifie:
return "Accès modérateur accordé"
elif niveau == NiveauUtilisateur.UTILISATEUR and age >= 18 and est_verifie:
return "Accès utilisateur accordé"
elif niveau == NiveauUtilisateur.UTILISATEUR and age < 18:
return "Accès utilisateur limité (mineur)"
elif not est_verifie:
return "Vérification du compte requise"
else:
return "Accès invité limité"
def calculer_remise(montant: float, code_client: str) -> float:
"""
Calcule une remise selon le type de client.
Args:
montant: Montant de l'achat
code_client: Code du type de client
Returns:
Montant de la remise
"""
# Pattern matching (Python 3.10+)
match code_client:
case "VIP":
return montant * 0.20
case "PREMIUM":
return montant * 0.15
case "STANDARD":
return montant * 0.10
case "NOUVEAU":
return montant * 0.05 if montant > 100 else 0
case *: # Cas par défaut
return 0
def traiter_commandes(commandes: List[Dict[str, Any]]) -> None:
"""
Traite une liste de commandes avec différentes boucles.
Args:
commandes: Liste des commandes à traiter
"""
print("=== Traitement des commandes ===n")
# Boucle for classique
for i, commande in enumerate(commandes, start=1):
print(f"Commande #{i}:")
print(f" Client: {commande['client']}")
print(f" Montant: {commande['montant']}€")
# Continue: passe à l'itération suivante
if commande['montant'] < 10:
print(" -> Montant trop faible, ignorée")
continue
# Break: sort de la boucle
if commande.get('annulee', False):
print(" -> Commande annulée, arrêt du traitement")
break
print(" -> Traitée avec succès")
print("n=== Analyse des montants ===n")
# Boucle while
total = 0
index = 0
while index < len(commandes) and not commandes[index].get('annulee', False):
total += commandes[index]['montant']
index += 1
print(f"Total des commandes traitées: {total}€")
print(f"Nombre de commandes: {index}")
def filtrer_et_transformer_donnees(
donnees: List[int],
seuil: int
) -> Dict[str, List[int]]:
"""
Filtre et catégorise des données.
Args:
donnees: Liste de nombres à traiter
seuil: Valeur de référence
Returns:
Dictionnaire avec les données catégorisées
"""
petits: List[int] = []
moyens: List[int] = []
grands: List[int] = []
for valeur in donnees:
if valeur < seuil:
petits.append(valeur)
elif valeur < seuil * 2:
moyens.append(valeur)
else:
grands.append(valeur)
return {
"petits": petits,
"moyens": moyens,
"grands": grands
}
# Compréhensions avancées
def comprehensions_avancees() -> None:
"""Démonstration de compréhensions avancées."""
# List comprehension avec condition
nombres = list(range(1, 21))
pairs = [n for n in nombres if n % 2 == 0]
carres_impairs = [n__2 for n in nombres if n % 2 != 0]
# Dict comprehension
carres_dict = {n: n__2 for n in range(1, 11)}
pairs_dict = {n: n__2 for n in range(1, 11) if n % 2 == 0}
# Set comprehension
chiffres_uniques = {n % 10 for n in range(100)}
# Nested comprehension (compréhension imbriquée)
matrice = [[i_j for j in range(1, 6)] for i in range(1, 6)]
# List comprehension avec if-else
categories = ["pair" if n % 2 == 0 else "impair" for n in range(1, 11)]
print("Nombres pairs:", pairs)
print("Carrés des impairs:", carres_impairs)
print("Dictionnaire carrés:", carres_dict)
print("Table de multiplication (5x5):")
for ligne in matrice:
print(ligne)
print("Catégories:", categories)
if __name__ == "__main__":
# Test vérification d'accès
print(verifier_acces(NiveauUtilisateur.UTILISATEUR, 25, True))
print(verifier_acces(NiveauUtilisateur.UTILISATEUR, 16, True))
print(verifier_acces(NiveauUtilisateur.MODERATEUR, 30, False))
print("n" + "="*50 + "n")
# Test calcul de remise
print(f"Remise VIP sur 200€: {calculer_remise(200, 'VIP')}€")
print(f"Remise NOUVEAU sur 50€: {calculer_remise(50, 'NOUVEAU')}€")
print(f"Remise NOUVEAU sur 150€: {calculer_remise(150, 'NOUVEAU')}€")
print("n" + "="*50 + "n")
# Test traitement de commandes
commandes = [
{"client": "Alice", "montant": 150.00},
{"client": "Bob", "montant": 5.00},
{"client": "Charlie", "montant": 75.50},
{"client": "David", "montant": 200.00, "annulee": True},
{"client": "Eve", "montant": 50.00},
]
traiter_commandes(commandes)
print("n" + "="*50 + "n")
# Test filtrage de données
donnees = [5, 15, 25, 35, 45, 55, 8, 12, 18]
resultat = filtrer_et_transformer_donnees(donnees, 20)
print("Données filtrées:")
for categorie, valeurs in resultat.items():
print(f" {categorie}: {valeurs}")
print("n" + "="*50 + "n")
comprehensions_avancees()
Fonctions et portée des variables
"""
functions_scope.py - Fonctions avancées et portée des variables
"""
from typing import List, Optional, Callable, Any
from functools import wraps
import time
# Variables globales (à éviter en production)
TAUX_TVA: float = 0.20
def calculer_prix_ttc(prix_ht: float, taux: Optional[float] = None) -> float:
"""
Calcule le prix TTC.
Args:
prix_ht: Prix hors taxes
taux: Taux de TVA personnalisé (utilise TAUX_TVA si None)
Returns:
Prix toutes taxes comprises
"""
taux_utilise = taux if taux is not None else TAUX_TVA
return prix_ht * (1 + taux_utilise)
# Arguments par défaut, *args et **kwargs
def creer_utilisateur(
nom: str,
prenom: str,
age: int,
email: Optional[str] = None,
*roles: str,
**metadata: Any
) -> dict[str, Any]:
"""
Crée un utilisateur avec paramètres flexibles.
Args:
nom: Nom de famille (requis)
prenom: Prénom (requis)
age: Age (requis)
email: Adresse email (optionnel)
*roles: Rôles variables de l'utilisateur
**metadata: Métadonnées additionnelles
Returns:
Dictionnaire représentant l'utilisateur
"""
utilisateur = {
"nom": nom,
"prenom": prenom,
"age": age,
"email": email or f"{prenom.lower()}.{nom.lower()}@example.com",
"roles": list(roles) if roles else ["user"],
"metadata": metadata
}
return utilisateur
# Fonctions de première classe (first-class functions)
def appliquer_operation(
nombres: List[float],
operation: Callable[[float], float]
) -> List[float]:
"""
Applique une opération à chaque nombre.
Args:
nombres: Liste de nombres
operation: Fonction à appliquer
Returns:
Liste des résultats
"""
return [operation(n) for n in nombres]
# Lambda functions
doubler = lambda x: x * 2
carre = lambda x: x ** 2
est_pair = lambda x: x % 2 == 0
# Closures (fermetures)
def creer_multiplicateur(facteur: int) -> Callable[[int], int]:
"""
Crée une fonction de multiplication.
Args:
facteur: Le facteur de multiplication
Returns:
Fonction qui multiplie par le facteur
"""
def multiplicateur(x: int) -> int:
return x * facteur
return multiplicateur
# Décorateurs
def chronometrer(func: Callable) -> Callable:
"""
Décorateur pour mesurer le temps d'exécution.
Args:
func: Fonction à chronométrer
Returns:
Fonction décorée
"""
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
debut = time.time()
resultat = func(*args, **kwargs)
fin = time.time()
print(f"{func.__name__} a pris {fin - debut:.4f} secondes")
return resultat
return wrapper
def valider_arguments(**validations: Callable) -> Callable:
"""
Décorateur pour valider les arguments d'une fonction.
Args:
**validations: Dictionnaire {nom_arg: fonction_validation}
Returns:
Décorateur
"""
def decorateur(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
# Récupération des noms d'arguments
import inspect
sig = inspect.signature(func)
bound_args = sig.bind(*args, **kwargs)
bound_args.apply_defaults()
# Validation
for nom_arg, validateur in validations.items():
if nom_arg in bound_args.arguments:
valeur = bound_args.arguments[nom_arg]
if not validateur(valeur):
raise ValueError(
f"Validation échouée pour {nom_arg}={valeur}"
)
return func(*args, **kwargs)
return wrapper
return decorateur
# Générateurs
def generer_fibonacci(n: int):
"""
Génère les n premiers nombres de Fibonacci.
Args:
n: Nombre d'éléments à générer
Yields:
Nombres de Fibonacci
"""
a, b = 0, 1
for * in range(n):
yield a
a, b = b, a + b
def lire_fichier_par_blocs(
fichier: str,
taille_bloc: int = 1024
):
"""
Lit un fichier par blocs (économie de mémoire).
Args:
fichier: Chemin du fichier
taille_bloc: Taille de chaque bloc
Yields:
Blocs de données
"""
with open(fichier, 'r', encoding='utf-8') as f:
while True:
bloc = f.read(taille_bloc)
if not bloc:
break
yield bloc
# Application pratique
@chronometrer
@valider_arguments(
montant=lambda x: x > 0,
reduction=lambda x: 0 <= x <= 1
)
def calculer_prix_final(
montant: float,
reduction: float = 0.0,
code_promo: Optional[str] = None
) -> float:
"""
Calcule le prix final avec réductions.
Args:
montant: Montant initial
reduction: Taux de réduction (0 à 1)
code_promo: Code promotionnel optionnel
Returns:
Prix final
"""
prix_reduit = montant * (1 - reduction)
# Application du code promo
if code_promo == "BIENVENUE":
prix_reduit *= 0.90
elif code_promo == "VIP":
prix_reduit *= 0.85
return calculer_prix_ttc(prix_reduit)
if __name__ == "__main__":
# Test création utilisateur
user1 = creer_utilisateur(
"Dupont",
"Marie",
28,
roles=["user", "moderator"],
ville="Paris",
pays="France"
)
print("Utilisateur créé:", user1)
print("n" + "="*50 + "n")
# Test fonctions de première classe
nombres = [1, 2, 3, 4, 5]
doubles = appliquer_operation(nombres, doubler)
carres = appliquer_operation(nombres, carre)
print(f"Nombres: {nombres}")
print(f"Doublés: {doubles}")
print(f"Carrés: {carres}")
print("n" + "="*50 + "n")
# Test closures
multiplier_par_3 = creer_multiplicateur(3)
multiplier_par_10 = creer_multiplicateur(10)
print(f"5 * 3 = {multiplier_par_3(5)}")
print(f"5 * 10 = {multiplier_par_10(5)}")
print("n" + "="*50 + "n")
# Test fonction décorée
prix_final = calculer_prix_final(100.0, 0.15, "BIENVENUE")
print(f"Prix final: {prix_final:.2f}€")
print("n" + "="*50 + "n")
# Test générateur
print("10 premiers nombres de Fibonacci:")
for nombre in generer_fibonacci(10):
print(nombre, end=" ")
print()
Projet pratique : Gestionnaire de tâches
"""
task_manager.py - Gestionnaire de tâches complet
"""
from typing import List, Optional, Dict, Any
from datetime import datetime, timedelta
from enum import Enum
import json
class Priorite(Enum):
"""Niveaux de priorité des tâches."""
BASSE = 1
MOYENNE = 2
HAUTE = 3
URGENTE = 4
class StatutTache(Enum):
"""Statuts possibles d'une tâche."""
A_FAIRE = "à faire"
EN_COURS = "en cours"
TERMINEE = "terminée"
ANNULEE = "annulée"
class Tache:
"""Représente une tâche individuelle."""
def __init__(
self,
titre: str,
description: str = "",
priorite: Priorite = Priorite.MOYENNE,
echeance: Optional[datetime] = None
) -> None:
"""
Initialise une tâche.
Args:
titre: Titre de la tâche
description: Description détaillée
priorite: Niveau de priorité
echeance: Date limite
"""
self.id: int = 0 # Sera assigné par le gestionnaire
self.titre = titre
self.description = description
self.priorite = priorite
self.statut = StatutTache.A_FAIRE
self.date_creation = datetime.now()
self.echeance = echeance
self.tags: List[str] = []
def est_en_retard(self) -> bool:
"""Vérifie si la tâche est en retard."""
if not self.echeance or self.statut == StatutTache.TERMINEE:
return False
return datetime.now() > self.echeance
def jours_restants(self) -> Optional[int]:
"""Calcule le nombre de jours restants."""
if not self.echeance:
return None
delta = self.echeance - datetime.now()
return delta.days
def to_dict(self) -> Dict[str, Any]:
"""Convertit la tâche en dictionnaire."""
return {
"id": self.id,
"titre": self.titre,
"description": self.description,
"priorite": self.priorite.name,
"statut": self.statut.value,
"date_creation": self.date_creation.isoformat(),
"echeance": self.echeance.isoformat() if self.echeance else None,
"tags": self.tags
}
def __str__(self) -> str:
"""Représentation textuelle de la tâche."""
retard = " [EN RETARD]" if self.est_en_retard() else ""
jours = self.jours_restants()
echeance_str = f" (J-{jours})" if jours is not None else ""
return (
f"[{self.id}] {self.titre} - "
f"{self.statut.value} - "
f"Priorité: {self.priorite.name}"
f"{echeance_str}{retard}"
)
class GestionnaireTaches:
"""Gestionnaire principal des tâches."""
def __init__(self) -> None:
"""Initialise le gestionnaire."""
self.taches: Dict[int, Tache] = {}
self._prochain_id: int = 1
def ajouter_tache(
self,
titre: str,
description: str = "",
priorite: Priorite = Priorite.MOYENNE,
echeance: Optional[datetime] = None,
tags: Optional[List[str]] = None
) -> Tache:
"""
Ajoute une nouvelle tâche.
Args:
titre: Titre de la tâche
description: Description
priorite: Niveau de priorité
echeance: Date limite
tags: Liste de tags
Returns:
La tâche créée
"""
tache = Tache(titre, description, priorite, echeance)
tache.id = self._prochain_id
if tags:
tache.tags = tags
self.taches[self._prochain_id] = tache
self._prochain_id += 1
return tache
def obtenir_tache(self, tache_id: int) -> Optional[Tache]:
"""Récupère une tâche par son ID."""
return self.taches.get(tache_id)
def supprimer_tache(self, tache_id: int) -> bool:
"""
Supprime une tâche.
Args:
tache_id: ID de la tâche à supprimer
Returns:
True si supprimée, False sinon
"""
if tache_id in self.taches:
del self.taches[tache_id]
return True
return False
def modifier_statut(
self,
tache_id: int,
nouveau_statut: StatutTache
) -> bool:
"""Modifie le statut d'une tâche."""
tache = self.obtenir_tache(tache_id)
if tache:
tache.statut = nouveau_statut
return True
return False
def lister_taches(
self,
statut: Optional[StatutTache] = None,
priorite: Optional[Priorite] = None,
tag: Optional[str] = None
) -> List[Tache]:
"""
Liste les tâches avec filtres optionnels.
Args:
statut: Filtrer par statut
priorite: Filtrer par priorité
tag: Filtrer par tag
Returns:
Liste des tâches correspondantes
"""
resultats = list(self.taches.values())
if statut:
resultats = [t for t in resultats if t.statut == statut]
if priorite:
resultats = [t for t in resultats if t.priorite == priorite]
if tag:
resultats = [t for t in resultats if tag in t.tags]
# Tri par priorité décroissante, puis par échéance
resultats.sort(
key=lambda t: (
-t.priorite.value,
t.echeance if t.echeance else datetime.max
)
)
return resultats
def taches_en_retard(self) -> List[Tache]:
"""Retourne les tâches en retard."""
return [t for t in self.taches.values() if t.est_en_retard()]
def taches_urgentes(self, jours: int = 3) -> List[Tache]:
"""
Retourne les tâches urgentes (échéance proche).
Args:
jours: Nombre de jours pour considérer comme urgent
Returns:
Liste des tâches urgentes
"""
resultats = []
for tache in self.taches.values():
jours_restants = tache.jours_restants()
if (
jours_restants is not None
and 0 <= jours_restants <= jours
and tache.statut != StatutTache.TERMINEE
):
resultats.append(tache)
return resultats
def statistiques(self) -> Dict[str, Any]:
"""Calcule des statistiques sur les tâches."""
total = len(self.taches)
if total == 0:
return {"total": 0}
stats_statut = {}
for statut in StatutTache:
count = len([t for t in self.taches.values() if t.statut == statut])
stats_statut[statut.value] = count
stats_priorite = {}
for priorite in Priorite:
count = len([t for t in self.taches.values() if t.priorite == priorite])
stats_priorite[priorite.name] = count
return {
"total": total,
"par_statut": stats_statut,
"par_priorite": stats_priorite,
"en_retard": len(self.taches_en_retard()),
"urgentes": len(self.taches_urgentes())
}
def sauvegarder(self, fichier: str) -> None:
"""
Sauvegarde les tâches dans un fichier JSON.
Args:
fichier: Chemin du fichier de sauvegarde
"""
donnees = {
"prochain_id": self._prochain_id,
"taches": [t.to_dict() for t in self.taches.values()]
}
with open(fichier, 'w', encoding='utf-8') as f:
json.dump(donnees, f, indent=2, ensure_ascii=False)
def charger(self, fichier: str) -> None:
"""
Charge les tâches depuis un fichier JSON.
Args:
fichier: Chemin du fichier à charger
"""
try:
with open(fichier, 'r', encoding='utf-8') as f:
donnees = json.load(f)
self._prochain_id = donnees["prochain_id"]
self.taches = {}
for t_dict in donnees["taches"]:
tache = Tache(
titre=t_dict["titre"],
description=t_dict["description"],
priorite=Priorite[t_dict["priorite"]]
)
tache.id = t_dict["id"]
tache.statut = StatutTache(t_dict["statut"])
tache.date_creation = datetime.fromisoformat(t_dict["date_creation"])
if t_dict["echeance"]:
tache.echeance = datetime.fromisoformat(t_dict["echeance"])
tache.tags = t_dict["tags"]
self.taches[tache.id] = tache
except FileNotFoundError:
print(f"Fichier {fichier} non trouvé, démarrage avec une liste vide")
def demo_gestionnaire() -> None:
"""Démonstration du gestionnaire de tâches."""
# Création du gestionnaire
gestionnaire = GestionnaireTaches()
# Ajout de tâches
print("=== Ajout de tâches ===n")
t1 = gestionnaire.ajouter_tache(
"Apprendre Python",
"Suivre un cours complet sur Python",
Priorite.HAUTE,
datetime.now() + timedelta(days=7),
["formation", "python"]
)
print(f"Tâche ajoutée: {t1}")
t2 = gestionnaire.ajouter_tache(
"Réunion d'équipe",
"Présenter le nouveau projet",
Priorite.URGENTE,
datetime.now() + timedelta(days=1),
["travail", "reunion"]
)
print(f"Tâche ajoutée: {t2}")
t3 = gestionnaire.ajouter_tache(
"Faire les courses",
"",
Priorite.BASSE,
datetime.now() + timedelta(days=2),
["personnel"]
)
print(f"Tâche ajoutée: {t3}")
# Liste toutes les tâches
print("n=== Toutes les tâches ===n")
for tache in gestionnaire.lister_taches():
print(tache)
# Modification de statut
print("n=== Modification de statuts ===n")
gestionnaire.modifier_statut(t2.id, StatutTache.EN_COURS)
print(f"Tâche {t2.id} passée en cours")
gestionnaire.modifier_statut(t3.id, StatutTache.TERMINEE)
print(f"Tâche {t3.id} terminée")
# Filtrage
print("n=== Tâches à faire ===n")
for tache in gestionnaire.lister_taches(statut=StatutTache.A_FAIRE):
print(tache)
print("n=== Tâches urgentes (3 jours) ===n")
for tache in gestionnaire.taches_urgentes(3):
print(tache)
# Statistiques
print("n=== Statistiques ===n")
stats = gestionnaire.statistiques()
print(f"Total: {stats['total']}")
print(f"Par statut: {stats['par_statut']}")
print(f"Par priorité: {stats['par_priorite']}")
print(f"En retard: {stats['en_retard']}")
print(f"Urgentes: {stats['urgentes']}")
# Sauvegarde
print("n=== Sauvegarde ===n")
gestionnaire.sauvegarder("/tmp/taches.json")
print("Tâches sauvegardées dans /tmp/taches.json")
if __name__ == "__main__":
demo_gestionnaire()
Tests avec pytest
"""
test_task_manager.py - Tests unitaires pour le gestionnaire de tâches
"""
import pytest
from datetime import datetime, timedelta
from task_manager import (
Tache,
GestionnaireTaches,
Priorite,
StatutTache
)
class TestTache:
"""Tests pour la classe Tache."""
def test_creation_tache(self):
"""Test de création d'une tâche."""
tache = Tache(
"Test",
"Description test",
Priorite.HAUTE
)
assert tache.titre == "Test"
assert tache.description == "Description test"
assert tache.priorite == Priorite.HAUTE
assert tache.statut == StatutTache.A_FAIRE
def test_tache_en_retard(self):
"""Test de détection de retard."""
# Tâche avec échéance passée
tache_retard = Tache(
"Retard",
echeance=datetime.now() - timedelta(days=1)
)
assert tache_retard.est_en_retard() is True
# Tâche avec échéance future
tache_future = Tache(
"Future",
echeance=datetime.now() + timedelta(days=1)
)
assert tache_future.est_en_retard() is False
# Tâche sans échéance
tache_sans = Tache("Sans échéance")
assert tache_sans.est_en_retard() is False
def test_jours_restants(self):
"""Test du calcul des jours restants."""
tache = Tache(
"Test",
echeance=datetime.now() + timedelta(days=5)
)
assert tache.jours_restants() == 5
tache_sans = Tache("Sans échéance")
assert tache_sans.jours_restants() is None
class TestGestionnaireTaches:
"""Tests pour le gestionnaire de tâches."""
@pytest.fixture
def gestionnaire(self):
"""Fixture pour créer un gestionnaire vide."""
return GestionnaireTaches()
@pytest.fixture
def gestionnaire_avec_taches(self, gestionnaire):
"""Fixture pour créer un gestionnaire avec des tâches."""
gestionnaire.ajouter_tache("Tâche 1", priorite=Priorite.HAUTE)
gestionnaire.ajouter_tache("Tâche 2", priorite=Priorite.BASSE)
gestionnaire.ajouter_tache("Tâche 3", priorite=Priorite.MOYENNE)
return gestionnaire
def test_ajouter_tache(self, gestionnaire):
"""Test d'ajout de tâche."""
tache = gestionnaire.ajouter_tache(
"Nouvelle tâche",
"Description",
Priorite.HAUTE
)
assert tache.id == 1
assert tache.titre == "Nouvelle tâche"
assert len(gestionnaire.taches) == 1
def test_obtenir_tache(self, gestionnaire_avec_taches):
"""Test de récupération de tâche."""
tache = gestionnaire_avec_taches.obtenir_tache(1)
assert tache is not None
assert tache.titre == "Tâche 1"
tache_inexistante = gestionnaire_avec_taches.obtenir_tache(999)
assert tache_inexistante is None
def test_supprimer_tache(self, gestionnaire_avec_taches):
"""Test de suppression de tâche."""
assert gestionnaire_avec_taches.supprimer_tache(1) is True
assert len(gestionnaire_avec_taches.taches) == 2
assert gestionnaire_avec_taches.supprimer_tache(999) is False
def test_modifier_statut(self, gestionnaire_avec_taches):
"""Test de modification de statut."""
assert gestionnaire_avec_taches.modifier_statut(1, StatutTache.TERMINEE)
tache = gestionnaire_avec_taches.obtenir_tache(1)
assert tache.statut == StatutTache.TERMINEE
def test_lister_taches_filtres(self, gestionnaire_avec_taches):
"""Test de filtrage des tâches."""
# Modifier quelques statuts
gestionnaire_avec_taches.modifier_statut(1, StatutTache.TERMINEE)
# Filtrer par statut
a_faire = gestionnaire_avec_taches.lister_taches(
statut=StatutTache.A_FAIRE
)
assert len(a_faire) == 2
# Filtrer par priorité
hautes = gestionnaire_avec_taches.lister_taches(
priorite=Priorite.HAUTE
)
assert len(hautes) == 1
def test_taches_urgentes(self, gestionnaire):
"""Test des tâches urgentes."""
# Tâche urgente (2 jours)
gestionnaire.ajouter_tache(
"Urgente",
echeance=datetime.now() + timedelta(days=2)
)
# Tâche non urgente (5 jours)
gestionnaire.ajouter_tache(
"Non urgente",
echeance=datetime.now() + timedelta(days=5)
)
urgentes = gestionnaire.taches_urgentes(3)
assert len(urgentes) == 1
def test_statistiques(self, gestionnaire_avec_taches):
"""Test des statistiques."""
gestionnaire_avec_taches.modifier_statut(1, StatutTache.TERMINEE)
stats = gestionnaire_avec_taches.statistiques()
assert stats["total"] == 3
assert stats["par_statut"]["terminée"] == 1
assert stats["par_statut"]["à faire"] == 2
if __name__ == "__main__":
pytest.main([__file__, "-v"])
Fichier pytest.ini
# pytest.ini - Configuration pytest
[pytest]
testpaths = tests
python_files = test**.py
python_classes = Test*
python_functions = test**
addopts =
-v
--strict-markers
--cov=.
--cov-report=html
--cov-report=term-missing
Bonnes pratiques et PEP
Configuration Black et MyPy
# pyproject.toml - Configuration des outils de qualité de code
[tool.black]
line-length = 88
target-version = ['py310']
include = '.pyi?$'
extend-exclude = '''
/(
# directories
.eggs
| .git
| .hg
| .mypy_cache
| .tox
| .venv
| build
| dist
)/
'''
[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
strict_equality = true
[tool.pytest.ini_options]
minversion = "7.0"
addopts = "-ra -q --strict-markers"
testpaths = ["tests"]
Guide des bonnes pratiques
"""
best_practices.py - Démonstration des bonnes pratiques Python
"""
# PEP 8: Style de code
# - 4 espaces pour l'indentation (pas de tabulations)
# - Lignes max 79 caractères (88 avec Black)
# - 2 lignes blanches entre fonctions de niveau module
# - 1 ligne blanche entre méthodes de classe
# PEP 257: Docstrings
# - Toujours utiliser des docstrings pour modules, classes, fonctions
# - Format: triple guillemets """
# - Première ligne: résumé court
# - Lignes suivantes: détails
from typing import List, Optional, Protocol
from dataclasses import dataclass
from enum import Enum
# PEP 484: Type Hints
# - Toujours typer les paramètres et retours de fonction
# - Utiliser Optional[T] pour les valeurs pouvant être None
# - Utiliser Union[T1, T2] pour plusieurs types possibles
@dataclass
class Config:
"""
Configuration de l'application.
Utilise @dataclass pour éviter le code boilerplate.
"""
nom: str
version: str
debug: bool = False
max_connexions: int = 100
class Loggable(Protocol):
"""
Protocol pour les objets loggables (PEP 544).
Les protocols définissent des interfaces sans héritage.
"""
def log(self, message: str) -> None:
"""Log un message."""
...
# Bonnes pratiques de nommage
# - snake_case pour fonctions et variables
# - PascalCase pour classes
# - MAJUSCULES pour constantes
# - *prefixe pour méthodes privées
CONSTANTE_GLOBALE: int = 42
def fonction_publique(parametre: str) -> str:
"""Fonction accessible publiquement."""
return *fonction_privee(parametre)
def *fonction_privee(parametre: str) -> str:
"""Fonction d'usage interne (convention)."""
return parametre.upper()
# Context managers (with statement)
class GestionnaireRessource:
"""
Gestionnaire de ressource utilisant le protocole context manager.
Assure le nettoyage même en cas d'erreur.
"""
def __init__(self, nom: str) -> None:
"""Initialise le gestionnaire."""
self.nom = nom
self.ressource: Optional[str] = None
def __enter__(self) -> "GestionnaireRessource":
"""Appelé au début du bloc with."""
print(f"Ouverture de {self.nom}")
self.ressource = f"Ressource {self.nom}"
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
"""Appelé à la fin du bloc with."""
print(f"Fermeture de {self.nom}")
self.ressource = None
# EAFP vs LBYL
# Python préfère EAFP (Easier to Ask for Forgiveness than Permission)
# plutôt que LBYL (Look Before You Leap)
def eafp_exemple(dictionnaire: dict, cle: str) -> Optional[str]:
"""Exemple EAFP (préféré en Python)."""
try:
return dictionnaire[cle]
except KeyError:
return None
def lbyl_exemple(dictionnaire: dict, cle: str) -> Optional[str]:
"""Exemple LBYL (moins pythonique)."""
if cle in dictionnaire:
return dictionnaire[cle]
return None
# List comprehensions vs boucles for
# Préférer les comprehensions quand c'est lisible
def filtrer_nombres_pythonique(nombres: List[int]) -> List[int]:
"""Version pythonique avec comprehension."""
return [n for n in nombres if n > 0 and n % 2 == 0]
def filtrer_nombres_verbeux(nombres: List[int]) -> List[int]:
"""Version verbeuse (éviter si possible)."""
resultat = []
for n in nombres:
if n > 0:
if n % 2 == 0:
resultat.append(n)
return resultat
if __name__ == "__main__":
# Test dataclass
config = Config("MonApp", "1.0.0", debug=True)
print(f"Config: {config}")
# Test context manager
with GestionnaireRessource("test.txt") as gestion:
print(f"Utilisation de: {gestion.ressource}")
# Comparaison EAFP vs LBYL
dico = {"cle1": "valeur1"}
print(eafp_exemple(dico, "cle1"))
print(eafp_exemple(dico, "cle_inexistante"))
# Comprehensions
nombres = [-2, -1, 0, 1, 2, 3, 4, 5, 6]
print(filtrer_nombres_pythonique(nombres))
Conclusion
Vous avez maintenant une base solide en Python avec :
- Syntaxe et types de base : Variables, opérateurs, types primitifs
- Structures de données : Listes, dictionnaires, tuples, sets
- Structures de contrôle : Conditions, boucles, comprehensions
- Fonctions avancées : Arguments flexibles, closures, décorateurs, générateurs
- Projet pratique : Gestionnaire de tâches complet
- Tests : pytest pour assurer la qualité
- Bonnes pratiques* : PEP 8, PEP 257, type hints
- Approfondir la programmation orientée objet (article suivant)
- Apprendre les frameworks web (FastAPI, Django)
- Maîtriser les bases de données avec SQLAlchemy
- Explorer la programmation asynchrone
- Découvrir le Data Science avec Pandas et NumPy
- Documentation officielle Python: https://docs.python.org/fr/3/
- PEP 8 Style Guide: https://pep8.org/
- Real Python tutorials: https://realpython.com/
- Python Package Index (PyPI): https://pypi.org/
Prochaines étapes
Ressources complémentaires
Continuez à pratiquer avec des projets réels et n’hésitez pas à explorer les bibliothèques de l’écosystème Python.