Itérateurs, Générateurs et Programmation Procédurale en Python

Itérateurs

1. Concept d'itération

L'itération est un processus de répétition où chaque étape dépend du résultat précédent, contrairement à une simple répétition.


# Répétition simple sans dépendance
while True:
    print('Répétition basique')

# Itération sur un tuple avec contrôle de boucle
donnees = (10, 20, 30)
position = 0
while position < len(donnees):
    print(donnees[position])
    position += 1

2. Besoin d'itérateurs et définitions

Les itérateurs fournissent une méthode uniforme pour parcourir des éléments sans utiliser d'index, particulièrement utile pour les structures non séquentielles comme les dictionnaires ou les fichiers.

Un objet itérable possède une méthode __iter__, tandis qu'un objet itérateur implémente à la fois __iter__ et __next__.


# Vérification des méthodes d'itération
chaine = 'exemple'
chaine.__iter__
collection = {1, 2, 3}
collection.__iter__
fichier = open('donnees.txt')
fichier.__iter__ et fichier.__next__

3. Utilisation des objets itérateurs

En créant un itérateur à partir d'un dictionnaire, on peut extraire les valeurs sans utiliser de clés directement.


map_dict = {'alpha': 1, 'beta': 2, 'gamma': 3}
iter_obj = map_dict.__iter__()
# L'appel à __iter__ sur un itérateur retourne lui-même
iter_obj.__iter__() is iter_obj  # True

val = next(iter_obj)
print(val)
# Répéter jusqu'à l'exception StopIteration

# Boucle while avec gestion d'erreur
iter_obj = map_dict.__iter__()
while True:
    try:
        cle = next(iter_obj)
        print(map_dict[cle])
    except StopIteration:
        break

4. Boucle for en Python

La boucle for automatise l'utilisation des itérateurs.


map_dict = {'alpha': 1, 'beta': 2, 'gamma': 3}
for cle in map_dict:
    print(map_dict[cle])

# Mécanisme interne de for:
# 1. Appel de map_dict.__iter__() pour obtenir un itérateur
# 2. Appels répétés à next() jusqu'à StopIteration

5. Avantages et inconvénients des itérateurs

Avantages : approche uniforme indépendante des index, calcul paresseux pour économiser la mémoire.

Inconvénients : pas de longueur accessible, parcours unique vers l'avant.

Générateurs

1. Définition des générateurs

Un générateur est créé par une fonction contenant le mot-clé yield, permettant de suspendre et reprendre l'exécution.


def createur_sequence():
    print('Début')
    yield 100
    print('Milieu')
    yield 200
    print('Fin')
    yield 300

gen = createur_sequence()
print(gen)  # Objet générateur

2. Générateurs comme itérateurs

Les générateurs implémentent les protocoles d'itération.


gen.__iter__
gen.__next__
valeur = next(gen)
print(valeur)

3. Exercices pratiques

Implémenter un générateur pour une suite arithmétique et simuler un pipeline de traitement de données.


# Exercice 1: Suite arithmétique personnalisée
def suite_arithmetique(debut, fin, pas=1):
    while debut < fin:
        yield debut
        debut += pas

# Utilisation dans une boucle
for elem in suite_arithmetique(0, 10, 2):
    print(elem)

# Exercice 2: Pipeline de fichiers avec filtrage
import time

def lire_fichier_continu(chemin):
    with open(chemin, 'rb') as f:
        f.seek(0, 2)
        while True:
            ligne = f.readline()
            if ligne:
                yield ligne
            else:
                time.sleep(0.1)

def filtrer_lignes(motif, flux):
    for ligne in flux:
        texte = ligne.decode('utf-8')
        if motif in texte:
            yield texte

# Application: recherche de 'erreur' dans un journal
for ligne in filtrer_lignes('erreur', lire_fichier_continu('journal.log')):
    print(ligne, end='')

4. Fonctions coroutines avec yield

Le yield peut être utilisé sous forme d'expression pour recevoir des valeurs.


def consommateur(nom):
    print(f'{nom} prêt à manger')
    repas = []
    while True:
        nourriture = yield repas
        print(f'{nom} a mangé {nourriture}')
        repas.append(nourriture)

coroutine = consommateur('Alice')
next(coroutine)  # Initialisation
coroutine.send('pain')
coroutine.send('fromage')

5. Exercices sur les coroutines

Décorateur pour initialiser les coroutines et implémentation d'une recherche récursive dans les fichiers.


# Décorateur d'initialisation
def initialise(func):
    def enveloppe(*args, **kwargs):
        gen = func(*args, **kwargs)
        next(gen)
        return gen
    return enveloppe

@initialise
def mangeur(nom):
    print(f'{nom} commence')
    historique = []
    while True:
        item = yield historique
        print(f'{nom} consomme {item}')
        historique.append(item)

# Exercice: Recherche dans l'arborescence de fichiers
import os

@initialise
def chercheur(cible):
    while True:
        chemin = yield
        for racine, _, fichiers in os.walk(chemin):
            for fichier in fichiers:
                chemin_complet = os.path.join(racine, fichier)
                cible.send(chemin_complet)

@initialise
def lecteur(cible):
    while True:
        chemin_fichier = yield
        with open(chemin_fichier, 'rb') as f:
            for ligne in f:
                resultat = cible.send((ligne, chemin_fichier))
                if resultat:
                    break

@initialise
def analyseur(motif, cible):
    marque = False
    while True:
        ligne, chemin = yield marque
        marque = False
        if motif.encode('utf-8') in ligne:
            cible.send(chemin)
            marque = True

@initialise
def afficheur():
    while True:
        chemin = yield
        print(chemin)

# Chaîne de traitement pour trouver 'Python' dans /etc
pipeline = chercheur(lecteur(analyseur('Python', afficheur())))
pipeline.send('/etc')

6. Résumé sur yield

yield transforme une fonction en itérateur, permettant de retourner plusieurs valeurs et de suspendre l'état d'exécution.

Programmation Procédurale

La programmation procédurale se concentre sur la définiiton de séquences d'étapes pour résoudre un problème, assimilable à une chaîne de montage.

Définition et caractéristiques : une approche mécaniste où chaque étape alimente la suivante, facilitant la simplification des problèmes complexes mais offrant une extensibilité limitée.

Applications : systèmes comme le noyau Linux, Git, ou httpd, où la stabilité prime sur la flexibilité.


# Exemple: Processus d'authentification utilisateur
# Étape 1: Collecte des identifiants
def saisir_identifiants():
    while True:
        login = input('Entrez votre nom d\'utilisateur: ').strip()
        if login.isalpha():
            break
        print('Le nom doit contenir uniquement des lettres')
    while True:
        mot_de_passe = input('Entrez votre mot de passe: ').strip()
        confirmation = input('Confirmez le mot de passe: ').strip()
        if mot_de_passe == confirmation:
            break
        print('Les mots de passe ne correspondent pas')
    return login, mot_de_passe

# Étape 2: Formatage des données
def formater_donnees(utilisateur, mot_de_passe):
    return f'{utilisateur}:{mot_de_passe}\n'

# Étape 3: Enregistrement dans un fichier
def sauvegarder(donnees_formatees, fichier):
    with open(fichier, 'a', encoding='utf-8') as f:
        f.write(donnees_formatees)

# Pipeline complet
def enregistrement():
    usr, pwd = saisir_identifiants()
    format_donnee = formater_donnees(usr, pwd)
    sauvegarder(format_donnee, 'utilisateurs.txt')

enregistrement()

# Extension avec rôle: montre la rigidité du modèle
def saisir_identifiants_avec_role():
    login, mot_de_passe = saisir_identifiants()
    roles = {'1': 'utilisateur', '2': 'administrateur'}
    while True:
        print('Choisissez votre rôle:')
        for code, role in roles.items():
            print(f'{code}: {role}')
        choix = input('Entrez le code du rôle: ').strip()
        if choix in roles:
            return login, mot_de_passe, roles[choix]
        print('Rôle invalide')

def formater_donnees_avec_role(utilisateur, mot_de_passe, role):
    return f'{utilisateur}:{mot_de_passe}:{role}\n'

def enregistrement_avec_role():
    usr, pwd, role = saisir_identifiants_avec_role()
    format_donnee = formater_donnees_avec_role(usr, pwd, role)
    sauvegarder(format_donnee, 'utilisateurs.txt')

enregistrement_avec_role()

L'approche procédurale structure le code en fonctions séquentielles, où la sortie d'une fonction devient l'entrée de la suivante, imitant un flux de données continu.

Étiquettes: Python itérateurs générateurs coroutines programmation procédurale

Publié le 1 juin à 15h09