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.