Debutant 20 min de lecture · 4 260 mots

Python pour débutants : Syntaxe de base et structures de données

Estimated reading time: 21 minutes

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

"""
basictypes.py - Démonstration des types de base en Python
"""

from typing import Final

# Constantes (convention: MAJUSCULES)
MAXUSERS: Final[int] = 100
PI: Final[float] = 3.14159
APPNAME: Final[str] = "MonApplication"

# Variables avec type hints
age: int = 25
prix: float = 19.99
nom: str = "Alice"
estactif: 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 afficherinformations(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: {APPNAME}")

if name == "main":
    afficherinformations(nom, age, ville)

Opérateurs et expressions

"""
operators.py - Opérateurs arithmétiques, logiques et de comparaison
"""

from typing import Union

def calculatricebasique(
    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 estdansintervalle(valeur: float, minval: float, maxval: float) -> bool:
    """Vérifie si une valeur est dans un intervalle."""
    return minval <= valeur <= maxval

# Opérateurs logiques
def peutvoter(age: int, nationalite: str, casierjudiciaire: bool) -> bool:
    """
    Détermine si une personne peut voter.

    Args:
        age: L'âge de la personne
        nationalite: La nationalité
        casierjudiciaire: True si casier judiciaire

    Returns:
        True si la personne peut voter
    """
    return (
        age >= 18
        and nationalite == "française"
        and not casierjudiciaire
    )

if name == "main":
    # Tests des opérateurs
    print(f"10 + 5 = {calculatricebasique(10, 5, '+')}")
    print(f"10 / 3 = {calculatricebasique(10, 3, '/')}")
    print(f"10 // 3 = {calculatricebasique(10, 3, '//')}")
    print(f"10 % 3 = {calculatricebasique(10, 3, '%')}")
    print(f"2  8 = {calculatricebasique(2, 8, '')}")

    print(f"n15 est entre 10 et 20: {estdansintervalle(15, 10, 20)}")
    print(f"Peut voter (25 ans, française, pas de casier): {peutvoter(25, 'française', False)}")

Structures de données intégrées

Listes (Lists)

"""
listsoperations.py - Opérations avancées sur les listes
"""

from typing import List, Optional, Any

def gestiontaches() -> 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
    elementsupprime = taches.pop()  # Supprime et retourne le dernier élément
    del taches[0]  # Supprime par index

    # Accès et modification
    premieretache = taches[0]
    dernieretache = taches[-1]
    taches[1] = "Nouvelle tâche"

    # Slicing (découpage)
    deuxpremieres = taches[:2]
    deuxdernieres = taches[-2:]
    milieu = taches[1:3]

    # List comprehension (compréhension de liste)
    nombrescarres = [n  2 for n in nombres]
    nombrespairs = [n for n in nombres if n % 2 == 0]

    print("Tâches actuelles:", taches)
    print("Carrés:", nombrescarres)
    print("Nombres pairs:", nombrespairs)

def filtrerettransformer(
    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 trouverelement(
    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":
    gestiontaches()

    nombres = [1, 5, 10, 15, 20, 25]
    resultat = filtrerettransformer(nombres, 10)
    print(f"nNombres > 10, multipliés par 2: {resultat}")

    index = trouverelement(nombres, 15)
    print(f"Index de 15: {index}")

Dictionnaires (Dictionaries)

"""
dictionariesoperations.py - Gestion avancée des dictionnaires
"""

from typing import Dict, List, Optional, Any

def gestionutilisateurs() -> 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"]
    emailsupprime = utilisateur.pop("email")

    # Itération
    for cle, valeur in utilisateur.items():
        print(f"{cle}: {valeur}")

    # Clés et valeurs
    touteslescles = list(utilisateur.keys())
    touteslesvaleurs = list(utilisateur.values())

    print(f"nClés: {touteslescles}")
    print(f"Valeurs: {touteslesvaleurs}")

class GestionnaireUtilisateurs:
    """Gestionnaire d'utilisateurs avec dictionnaire."""

    def init(self) -> None:
        """Initialise le gestionnaire."""
        self.utilisateurs: Dict[int, Dict[str, Any]] = {}
        self.prochainid: int = 1

    def ajouterutilisateur(
        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éé
        """
        utilisateurid = self.prochainid
        self.utilisateurs[utilisateurid] = {
            "nom": nom,
            "prenom": prenom,
            "email": email,
            "age": age
        }
        self.prochainid += 1
        return utilisateurid

    def obtenirutilisateur(self, userid: int) -> Optional[Dict[str, Any]]:
        """Récupère un utilisateur par son ID."""
        return self.utilisateurs.get(userid)

    def rechercherparage(self, agemin: int, agemax: int) -> List[Dict[str, Any]]:
        """
        Recherche des utilisateurs par tranche d'âge.

        Args:
            agemin: Age minimum
            agemax: Age maximum

        Returns:
            Liste des utilisateurs correspondants
        """
        return [
            {user, "id": uid}
            for uid, user in self.utilisateurs.items()
            if agemin <= user["age"] <= agemax
        ]

    def statistiques(self) -> Dict[str, Any]:
        """Calcule des statistiques sur les utilisateurs."""
        if not self.utilisateurs:
            return {"total": 0, "agemoyen": 0}

        ages = [user["age"] for user in self.utilisateurs.values()]
        return {
            "total": len(self.utilisateurs),
            "agemoyen": sum(ages) / len(ages),
            "agemin": min(ages),
            "agemax": max(ages)
        }

if name == "main":
    gestionutilisateurs()

    print("n" + "="50)
    print("Gestionnaire d'utilisateurs")
    print("="50 + "n")

    gestionnaire = GestionnaireUtilisateurs()

    # Ajout d'utilisateurs
    id1 = gestionnaire.ajouterutilisateur("Dupont", "Marie", "marie@example.com", 28)
    id2 = gestionnaire.ajouterutilisateur("Martin", "Pierre", "pierre@example.com", 35)
    id3 = gestionnaire.ajouterutilisateur("Durand", "Sophie", "sophie@example.com", 22)

    # Recherche
    jeunes = gestionnaire.rechercherparage(20, 30)
    print(f"Utilisateurs entre 20 et 30 ans: {jeunes}")

    # Statistiques
    stats = gestionnaire.statistiques()
    print(f"nStatistiques: {stats}")

Tuples et Sets

"""
tuplessets.py - Tuples et ensembles
"""

from typing import Tuple, Set, FrozenSet, List

# TUPLES - Immuables, ordonnés
def coordonneesgps() -> Tuple[float, float]:
    """
    Retourne des coordonnées GPS (latitude, longitude).

    Returns:
        Un tuple de coordonnées
    """
    return 48.8566, 2.3522  # Paris

def informationspersonne() -> 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 supprimerdoublons(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 operationsensembles() -> 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)
        tousfruits = fruits1 | fruits2
        # ou: tousfruits = fruits1.union(fruits2)

        # Intersection (éléments communs)
        fruitscommuns = fruits1 & fruits2
        # ou: fruitscommuns = fruits1.intersection(fruits2)

        # Différence (dans fruits1 mais pas dans fruits2)
        uniquementfruits1 = fruits1 - fruits2
        # ou: uniquementfruits1 = fruits1.difference(fruits2)

        # Différence symétrique (dans l'un ou l'autre, mais pas les deux)
        fruitsdifferents = fruits1 ^ fruits2
        # ou: fruitsdifferents = fruits1.symmetricdifference(fruits2)

        print("Fruits1:", fruits1)
        print("Fruits2:", fruits2)
        print("Union:", tousfruits)
        print("Intersection:", fruitscommuns)
        print("Différence:", uniquementfruits1)
        print("Différence symétrique:", fruitsdifferents)

    @staticmethod
    def tagsarticles() -> None:
        """Exemple pratique: gestion de tags d'articles."""

        article1tags: Set[str] = {"python", "programmation", "debutant"}
        article2tags: Set[str] = {"python", "web", "django"}
        article3tags: Set[str] = {"javascript", "web", "react"}

        # Trouver les tags communs à tous les articles Python
        tagspython = article1tags & article2tags
        print(f"nTags communs aux articles Python: {tagspython}")

        # Tous les tags uniques
        toustags = article1tags | article2tags | article3tags
        print(f"Tous les tags: {toustags}")

        # Tags spécifiques à Python
        tagsspecifiquespython = (article1tags | article2tags) - article3tags
        print(f"Tags spécifiques Python: {tagsspecifiquespython}")

# FrozenSet - Ensemble immuable (peut être clé de dictionnaire)
def creerpermissions() -> FrozenSet[str]:
    """Crée un ensemble immuable de permissions."""
    return frozenset(["lire", "ecrire", "executer"])

if name == "main":
    # Tuples
    lat, lon = coordonneesgps()
    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)
    listeavecdoublons = ["a", "b", "c", "a", "b", "d"]
    sansdoublons = GestionEnsembles.supprimerdoublons(listeavecdoublons)
    print(f"Liste originale: {listeavecdoublons}")
    print(f"Sans doublons: {sansdoublons}")

    print("n" + "="50)
    GestionEnsembles.operationsensembles()
    GestionEnsembles.tagsarticles()

    # FrozenSet
    perms = creerpermissions()
    print(f"nPermissions: {perms}")

Structures de contrôle

Conditions et boucles

"""
controlstructures.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 verifieracces(
    niveau: NiveauUtilisateur,
    age: int,
    estverifie: bool
) -> str:
    """
    Vérifie les droits d'accès d'un utilisateur.

    Args:
        niveau: Niveau de l'utilisateur
        age: Age de l'utilisateur
        estverifie: 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 estverifie:
        return "Accès modérateur accordé"
    elif niveau == NiveauUtilisateur.UTILISATEUR and age >= 18 and estverifie:
        return "Accès utilisateur accordé"
    elif niveau == NiveauUtilisateur.UTILISATEUR and age < 18:
        return "Accès utilisateur limité (mineur)"
    elif not estverifie:
        return "Vérification du compte requise"
    else:
        return "Accès invité limité"

def calculerremise(montant: float, codeclient: str) -> float:
    """
    Calcule une remise selon le type de client.

    Args:
        montant: Montant de l'achat
        codeclient: Code du type de client

    Returns:
        Montant de la remise
    """
    # Pattern matching (Python 3.10+)
    match codeclient:
        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 traitercommandes(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 filtrerettransformerdonnees(
    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 comprehensionsavancees() -> 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]
    carresimpairs = [n2 for n in nombres if n % 2 != 0]

    # Dict comprehension
    carresdict = {n: n2 for n in range(1, 11)}
    pairsdict = {n: n2 for n in range(1, 11) if n % 2 == 0}

    # Set comprehension
    chiffresuniques = {n % 10 for n in range(100)}

    # Nested comprehension (compréhension imbriquée)
    matrice = [[ij 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:", carresimpairs)
    print("Dictionnaire carrés:", carresdict)
    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(verifieracces(NiveauUtilisateur.UTILISATEUR, 25, True))
    print(verifieracces(NiveauUtilisateur.UTILISATEUR, 16, True))
    print(verifieracces(NiveauUtilisateur.MODERATEUR, 30, False))

    print("n" + "="50 + "n")

    # Test calcul de remise
    print(f"Remise VIP sur 200€: {calculerremise(200, 'VIP')}€")
    print(f"Remise NOUVEAU sur 50€: {calculerremise(50, 'NOUVEAU')}€")
    print(f"Remise NOUVEAU sur 150€: {calculerremise(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},
    ]
    traitercommandes(commandes)

    print("n" + "="50 + "n")

    # Test filtrage de données
    donnees = [5, 15, 25, 35, 45, 55, 8, 12, 18]
    resultat = filtrerettransformerdonnees(donnees, 20)
    print("Données filtrées:")
    for categorie, valeurs in resultat.items():
        print(f"  {categorie}: {valeurs}")

    print("n" + "="50 + "n")

    comprehensionsavancees()

Fonctions et portée des variables

"""
functionsscope.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)
TAUXTVA: float = 0.20

def calculerprixttc(prixht: float, taux: Optional[float] = None) -> float:
    """
    Calcule le prix TTC.

    Args:
        prixht: Prix hors taxes
        taux: Taux de TVA personnalisé (utilise TAUXTVA si None)

    Returns:
        Prix toutes taxes comprises
    """
    tauxutilise = taux if taux is not None else TAUXTVA
    return prixht  (1 + tauxutilise)

# Arguments par défaut, args et kwargs
def creerutilisateur(
    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 appliqueroperation(
    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
estpair = lambda x: x % 2 == 0

# Closures (fermetures)
def creermultiplicateur(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 validerarguments(validations: Callable) -> Callable:
    """
    Décorateur pour valider les arguments d'une fonction.

    Args:
        validations: Dictionnaire {nomarg: fonctionvalidation}

    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)
            boundargs = sig.bind(args, kwargs)
            boundargs.applydefaults()

            # Validation
            for nomarg, validateur in validations.items():
                if nomarg in boundargs.arguments:
                    valeur = boundargs.arguments[nomarg]
                    if not validateur(valeur):
                        raise ValueError(
                            f"Validation échouée pour {nomarg}={valeur}"
                        )

            return func(args, kwargs)
        return wrapper
    return decorateur

# Générateurs
def genererfibonacci(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 lirefichierparblocs(
    fichier: str,
    taillebloc: int = 1024
):
    """
    Lit un fichier par blocs (économie de mémoire).

    Args:
        fichier: Chemin du fichier
        taillebloc: Taille de chaque bloc

    Yields:
        Blocs de données
    """
    with open(fichier, 'r', encoding='utf-8') as f:
        while True:
            bloc = f.read(taillebloc)
            if not bloc:
                break
            yield bloc

# Application pratique
@chronometrer
@validerarguments(
    montant=lambda x: x > 0,
    reduction=lambda x: 0 <= x <= 1
)
def calculerprixfinal(
    montant: float,
    reduction: float = 0.0,
    codepromo: Optional[str] = None
) -> float:
    """
    Calcule le prix final avec réductions.

    Args:
        montant: Montant initial
        reduction: Taux de réduction (0 à 1)
        codepromo: Code promotionnel optionnel

    Returns:
        Prix final
    """
    prixreduit = montant  (1 - reduction)

    # Application du code promo
    if codepromo == "BIENVENUE":
        prixreduit = 0.90
    elif codepromo == "VIP":
        prixreduit = 0.85

    return calculerprixttc(prixreduit)

if name == "main":
    # Test création utilisateur
    user1 = creerutilisateur(
        "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 = appliqueroperation(nombres, doubler)
    carres = appliqueroperation(nombres, carre)
    print(f"Nombres: {nombres}")
    print(f"Doublés: {doubles}")
    print(f"Carrés: {carres}")

    print("n" + "="50 + "n")

    # Test closures
    multiplierpar3 = creermultiplicateur(3)
    multiplierpar10 = creermultiplicateur(10)
    print(f"5  3 = {multiplierpar3(5)}")
    print(f"5  10 = {multiplierpar10(5)}")

    print("n" + "="50 + "n")

    # Test fonction décorée
    prixfinal = calculerprixfinal(100.0, 0.15, "BIENVENUE")
    print(f"Prix final: {prixfinal:.2f}€")

    print("n" + "="50 + "n")

    # Test générateur
    print("10 premiers nombres de Fibonacci:")
    for nombre in genererfibonacci(10):
        print(nombre, end=" ")
    print()

Projet pratique : Gestionnaire de tâches

"""
taskmanager.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."""
    AFAIRE = "à faire"
    ENCOURS = "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.AFAIRE
        self.datecreation = datetime.now()
        self.echeance = echeance
        self.tags: List[str] = []

    def estenretard(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 joursrestants(self) -> Optional[int]:
        """Calcule le nombre de jours restants."""
        if not self.echeance:
            return None
        delta = self.echeance - datetime.now()
        return delta.days

    def todict(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,
            "datecreation": self.datecreation.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.estenretard() else ""
        jours = self.joursrestants()
        echeancestr = 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"{echeancestr}{retard}"
        )

class GestionnaireTaches:
    """Gestionnaire principal des tâches."""

    def init(self) -> None:
        """Initialise le gestionnaire."""
        self.taches: Dict[int, Tache] = {}
        self.prochainid: int = 1

    def ajoutertache(
        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.prochainid
        if tags:
            tache.tags = tags

        self.taches[self.prochainid] = tache
        self.prochainid += 1

        return tache

    def obtenirtache(self, tacheid: int) -> Optional[Tache]:
        """Récupère une tâche par son ID."""
        return self.taches.get(tacheid)

    def supprimertache(self, tacheid: int) -> bool:
        """
        Supprime une tâche.

        Args:
            tacheid: ID de la tâche à supprimer

        Returns:
            True si supprimée, False sinon
        """
        if tacheid in self.taches:
            del self.taches[tacheid]
            return True
        return False

    def modifierstatut(
        self,
        tacheid: int,
        nouveaustatut: StatutTache
    ) -> bool:
        """Modifie le statut d'une tâche."""
        tache = self.obtenirtache(tacheid)
        if tache:
            tache.statut = nouveaustatut
            return True
        return False

    def listertaches(
        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 tachesenretard(self) -> List[Tache]:
        """Retourne les tâches en retard."""
        return [t for t in self.taches.values() if t.estenretard()]

    def tachesurgentes(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():
            joursrestants = tache.joursrestants()
            if (
                joursrestants is not None
                and 0 <= joursrestants <= 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}

        statsstatut = {}
        for statut in StatutTache:
            count = len([t for t in self.taches.values() if t.statut == statut])
            statsstatut[statut.value] = count

        statspriorite = {}
        for priorite in Priorite:
            count = len([t for t in self.taches.values() if t.priorite == priorite])
            statspriorite[priorite.name] = count

        return {
            "total": total,
            "parstatut": statsstatut,
            "parpriorite": statspriorite,
            "enretard": len(self.tachesenretard()),
            "urgentes": len(self.tachesurgentes())
        }

    def sauvegarder(self, fichier: str) -> None:
        """
        Sauvegarde les tâches dans un fichier JSON.

        Args:
            fichier: Chemin du fichier de sauvegarde
        """
        donnees = {
            "prochainid": self.prochainid,
            "taches": [t.todict() for t in self.taches.values()]
        }

        with open(fichier, 'w', encoding='utf-8') as f:
            json.dump(donnees, f, indent=2, ensureascii=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.prochainid = donnees["prochainid"]
            self.taches = {}

            for tdict in donnees["taches"]:
                tache = Tache(
                    titre=tdict["titre"],
                    description=tdict["description"],
                    priorite=Priorite[tdict["priorite"]]
                )
                tache.id = tdict["id"]
                tache.statut = StatutTache(tdict["statut"])
                tache.datecreation = datetime.fromisoformat(tdict["datecreation"])
                if tdict["echeance"]:
                    tache.echeance = datetime.fromisoformat(tdict["echeance"])
                tache.tags = tdict["tags"]

                self.taches[tache.id] = tache

        except FileNotFoundError:
            print(f"Fichier {fichier} non trouvé, démarrage avec une liste vide")

def demogestionnaire() -> 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.ajoutertache(
        "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.ajoutertache(
        "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.ajoutertache(
        "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.listertaches():
        print(tache)

    # Modification de statut
    print("n=== Modification de statuts ===n")
    gestionnaire.modifierstatut(t2.id, StatutTache.ENCOURS)
    print(f"Tâche {t2.id} passée en cours")

    gestionnaire.modifierstatut(t3.id, StatutTache.TERMINEE)
    print(f"Tâche {t3.id} terminée")

    # Filtrage
    print("n=== Tâches à faire ===n")
    for tache in gestionnaire.listertaches(statut=StatutTache.AFAIRE):
        print(tache)

    print("n=== Tâches urgentes (3 jours) ===n")
    for tache in gestionnaire.tachesurgentes(3):
        print(tache)

    # Statistiques
    print("n=== Statistiques ===n")
    stats = gestionnaire.statistiques()
    print(f"Total: {stats['total']}")
    print(f"Par statut: {stats['parstatut']}")
    print(f"Par priorité: {stats['parpriorite']}")
    print(f"En retard: {stats['enretard']}")
    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":
    demogestionnaire()

Tests avec pytest

"""
testtaskmanager.py - Tests unitaires pour le gestionnaire de tâches
"""

import pytest
from datetime import datetime, timedelta
from taskmanager import (
    Tache,
    GestionnaireTaches,
    Priorite,
    StatutTache
)

class TestTache:
    """Tests pour la classe Tache."""

    def testcreationtache(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.AFAIRE

    def testtacheenretard(self):
        """Test de détection de retard."""
        # Tâche avec échéance passée
        tacheretard = Tache(
            "Retard",
            echeance=datetime.now() - timedelta(days=1)
        )
        assert tacheretard.estenretard() is True

        # Tâche avec échéance future
        tachefuture = Tache(
            "Future",
            echeance=datetime.now() + timedelta(days=1)
        )
        assert tachefuture.estenretard() is False

        # Tâche sans échéance
        tachesans = Tache("Sans échéance")
        assert tachesans.estenretard() is False

    def testjoursrestants(self):
        """Test du calcul des jours restants."""
        tache = Tache(
            "Test",
            echeance=datetime.now() + timedelta(days=5)
        )
        assert tache.joursrestants() == 5

        tachesans = Tache("Sans échéance")
        assert tachesans.joursrestants() 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 gestionnaireavectaches(self, gestionnaire):
        """Fixture pour créer un gestionnaire avec des tâches."""
        gestionnaire.ajoutertache("Tâche 1", priorite=Priorite.HAUTE)
        gestionnaire.ajoutertache("Tâche 2", priorite=Priorite.BASSE)
        gestionnaire.ajoutertache("Tâche 3", priorite=Priorite.MOYENNE)
        return gestionnaire

    def testajoutertache(self, gestionnaire):
        """Test d'ajout de tâche."""
        tache = gestionnaire.ajoutertache(
            "Nouvelle tâche",
            "Description",
            Priorite.HAUTE
        )

        assert tache.id == 1
        assert tache.titre == "Nouvelle tâche"
        assert len(gestionnaire.taches) == 1

    def testobtenirtache(self, gestionnaireavectaches):
        """Test de récupération de tâche."""
        tache = gestionnaireavectaches.obtenirtache(1)
        assert tache is not None
        assert tache.titre == "Tâche 1"

        tacheinexistante = gestionnaireavectaches.obtenirtache(999)
        assert tacheinexistante is None

    def testsupprimertache(self, gestionnaireavectaches):
        """Test de suppression de tâche."""
        assert gestionnaireavectaches.supprimertache(1) is True
        assert len(gestionnaireavectaches.taches) == 2

        assert gestionnaireavectaches.supprimertache(999) is False

    def testmodifierstatut(self, gestionnaireavectaches):
        """Test de modification de statut."""
        assert gestionnaireavectaches.modifierstatut(1, StatutTache.TERMINEE)

        tache = gestionnaireavectaches.obtenirtache(1)
        assert tache.statut == StatutTache.TERMINEE

    def testlistertachesfiltres(self, gestionnaireavectaches):
        """Test de filtrage des tâches."""
        # Modifier quelques statuts
        gestionnaireavectaches.modifierstatut(1, StatutTache.TERMINEE)

        # Filtrer par statut
        afaire = gestionnaireavectaches.listertaches(
            statut=StatutTache.AFAIRE
        )
        assert len(afaire) == 2

        # Filtrer par priorité
        hautes = gestionnaireavectaches.listertaches(
            priorite=Priorite.HAUTE
        )
        assert len(hautes) == 1

    def testtachesurgentes(self, gestionnaire):
        """Test des tâches urgentes."""
        # Tâche urgente (2 jours)
        gestionnaire.ajoutertache(
            "Urgente",
            echeance=datetime.now() + timedelta(days=2)
        )

        # Tâche non urgente (5 jours)
        gestionnaire.ajoutertache(
            "Non urgente",
            echeance=datetime.now() + timedelta(days=5)
        )

        urgentes = gestionnaire.tachesurgentes(3)
        assert len(urgentes) == 1

    def teststatistiques(self, gestionnaireavectaches):
        """Test des statistiques."""
        gestionnaireavectaches.modifierstatut(1, StatutTache.TERMINEE)

        stats = gestionnaireavectaches.statistiques()

        assert stats["total"] == 3
        assert stats["parstatut"]["terminée"] == 1
        assert stats["parstatut"]["à faire"] == 2

if name == "main":
    pytest.main([file, "-v"])

Fichier pytest.ini

# pytest.ini - Configuration pytest
[pytest]
testpaths = tests
pythonfiles = test.py
pythonclasses = Test
pythonfunctions = 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
  | .mypycache
  | .tox
  | .venv
  | build
  | dist
)/
'''

[tool.mypy]
pythonversion = "3.10"
warnreturnany = true
warnunusedconfigs = true
disallowuntypeddefs = true
disallowincompletedefs = true
checkuntypeddefs = true
noimplicitoptional = true
warnredundantcasts = true
warnunusedignores = true
warnnoreturn = true
strictequality = true

[tool.pytest.inioptions]
minversion = "7.0"
addopts = "-ra -q --strict-markers"
testpaths = ["tests"]

Guide des bonnes pratiques

"""
bestpractices.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
    maxconnexions: 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
# - snakecase pour fonctions et variables
# - PascalCase pour classes
# - MAJUSCULES pour constantes
# - prefixe pour méthodes privées

CONSTANTEGLOBALE: int = 42

def fonctionpublique(parametre: str) -> str:
    """Fonction accessible publiquement."""
    return fonctionprivee(parametre)

def fonctionprivee(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, exctype, excval, exctb) -> 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 eafpexemple(dictionnaire: dict, cle: str) -> Optional[str]:
    """Exemple EAFP (préféré en Python)."""
    try:
        return dictionnaire[cle]
    except KeyError:
        return None

def lbylexemple(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 filtrernombrespythonique(nombres: List[int]) -> List[int]:
    """Version pythonique avec comprehension."""
    return [n for n in nombres if n > 0 and n % 2 == 0]

def filtrernombresverbeux(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(eafpexemple(dico, "cle1"))
    print(eafpexemple(dico, "cleinexistante"))

    # Comprehensions
    nombres = [-2, -1, 0, 1, 2, 3, 4, 5, 6]
    print(filtrernombrespythonique(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
  • Prochaines étapes

  • 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
  • Ressources complémentaires

  • 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/

Continuez à pratiquer avec des projets réels et n’hésitez pas à explorer les bibliothèques de l’écosystème Python.

Une remarque, un retour ?

Cet article est vivant — corrections, contre-arguments et retours de production sont les bienvenus. Trois canaux, choisissez celui qui vous convient.