Patron d'itérateur Python

Imaginez que vous avez un bocal rempli de bonbons de différentes couleurs. Vous voulez goûter ces bonbons un par un, mais vous ne voulez pas verser tous les bonbons d'un seul coup. À ce moment-là, vous plongez la main dans le bocal et prenez un bonbon à la fois—ce processus est l'itération.

En programmation,le patron de conception Itérateurest un patron de conception qui fournit un moyen d'accéder séquentiellement aux éléments d'un objet collection sans exposer sa représentation interne.

Concepts fondamentaux

Le patron de conception Itérateur se compose de deux composants principaux :

  1. Itérateur: Responsable de définir l'interface pour accéder aux éléments et les parcourir
  2. Itérable: Fournit une méthode pour créer des itérateurs

En Python, le patron de conception Itérateur a été profondément intégré aux fonctionnalités de base du langage, rendant notre programmation plus élégante et intuitive.


Pourquoi avons-nous besoin d'itérateurs ?

Limites de l'approche traditionnelle

Regardons d'abord un exemple qui n'utilise pas d'itérateurs :

Exemple

# Une simple classe de collection de livres
class BookCollection:
    def __init__(self):
        self.books = ["Python Introduction", "Introduction to Algorithms", "Design Patterns", "Data Structures"]
   
    def get_books(self):
        return self.books

# Utiliser cette collection
collection = BookCollection()
books = collection.get_books()

# Parcourir books - expose les détails d'implémentation internes
for i in range(len(books)):
    print(books[i])

Analyse du problème :

  • Le code client doit connaître la structure interne de la collection (une liste)
  • Si l'implémentation interne de la collection change (par exemple, d'une liste à un dictionnaire), tout le code client doit être modifié
  • La logique de parcours est étroitement couplée à l'implémentation de la collection

Avantages des itérateurs

Exemple

# Utilisation d'itérateurs
for book in collection:
    print(book)

Avantages de l'utilisation des itérateurs :

  • Encapsulation: Masque l'implémentation interne de la collection
  • Interface unifiée: Différents types de collections peuvent utiliser la même méthode de parcours
  • Flexibilité: Vous pouvez facilement changer l'implémentation d'une collection
  • Parcours multiples pris en charge: Plusieurs opérations de parcours peuvent être effectuées simultanément

Le protocole des itérateurs en Python

Python implémente le protocole des itérateurs via deux méthodes spéciales :

__iter__()Méthode

  • Renvoie un objet itérateur
  • Un objet itérable doit implémenter cette méthode

__next__()Méthode

  • Renvoie l'élément suivant de la séquence
  • Lorsqu'il n'y a plus d'éléments, leverStopIterationune exception

Comprenons comment fonctionnent les itérateurs à l'aide d'un organigramme :


Création d'itérateurs personnalisés

Méthode 1 : Implémenter un itérateur avec une classe

Créons un itérateur de livre personnalisé :

Exemple

class BookIterator:
    """Classe d'itérateur de livre"""
   
    def __init__(self, books):
        self.books = books
        self.index = 0
   
    def __iter__(self):
        """Renvoie l'itérateur lui-même"""
        return self
   
    def __next__(self):
        """Renvoie le livre suivant, ou lève StopIteration s'il n'y a plus de livres"""
        if self.index < len(self.books):
            book = self.books[self.index]
            self.index += 1
            return book
        else:
            raise StopIteration

class BookCollection:
    """Classe de collection de livres itérable"""
   
    def __init__(self):
        self.books = ["Python Introduction", "Introduction to Algorithms", "Design Patterns", "Data Structures"]
   
    def __iter__(self):
        """Renvoie une instance d'itérateur"""
        return BookIterator(self.books)

# Utiliser l'itérateur personnalisé
collection = BookCollection()
for book in collection:
    print(f"Lecture : {book}")

Sortie :

正在阅读: Python入门
正在阅读: 算法导论
正在阅读: 设计模式
正在阅读: 数据结构

Méthode 2 : Utilisation de fonctions génératrices

Python offre un moyen plus concis — les fonctions génératrices :

Exemple

class BookCollection:
    def __init__(self):
        self.books = ["Python Introduction", "Introduction to Algorithms", "Design Patterns", "Data Structures"]
   
    def __iter__(self):
        """Créer un itérateur à l'aide d'une fonction génératrice"""
        for book in self.books:
            yield book

# L'utilisation est exactement la même
collection = BookCollection()
for book in collection:
    print(f"Lecture : {book}")

Avantages des générateurs :

  • Code plus propre
  • Gère automatiquement la sauvegarde de l'état
  • Meilleures performances

Cas d'utilisation pratiques pour les itérateurs

Scénario 1 : Lecture de données paginées

Exemple

class PaginatedData:
    """Simuler un itérateur de données paginées"""
   
    def __init__(self, total_items, page_size=3):
        self.total_items = total_items
        self.page_size = page_size
        self.current_page = 0
   
    def __iter__(self):
        return self
   
    def __next__(self):
        start = self.current_page * self.page_size
        end = start + self.page_size
       
        if start >= self.total_items:
            raise StopIteration
       
        # Simuler la lecture d'une page de données depuis la base de données
        page_data = list(range(start, min(end, self.total_items)))
        self.current_page += 1
       
        return page_data

# Utiliser l'itérateur paginé
paginator = PaginatedData(10, 3)  # 10 enregistrements au total, 3 par page
for page_num, page_data in enumerate(paginator, 1):
    print(f"Données de la page {page_num} : {page_data}")

Sortie :

第1页数据: [0, 1, 2]
第2页数据: [3, 4, 5]
第3页数据: [6, 7, 8]
第4页数据: [9]

Scénario 2 : Génération de séquences infinies

Exemple

class FibonacciIterator:
    """Itérateur de la suite de Fibonacci"""
   
    def __init__(self, max_count=10):
        self.max_count = max_count
        self.count = 0
        self.a, self.b = 0, 1
   
    def __iter__(self):
        return self
   
    def __next__(self):
        if self.count >= self.max_count:
            raise StopIteration
       
        result = self.a
        self.a, self.b = self.b, self.a + self.b
        self.count += 1
        return result

# Générer la suite de Fibonacci
fib = FibonacciIterator(8)
print("Suite de Fibonacci :", list(fib))

Sortie :

斐波那契数列: [0, 1, 1, 2, 3, 5, 8, 13]

Utilitaires d'itérateurs intégrés

Python fournit un riche ensemble de fonctions intégrées pour travailler avec les itérateurs :

iter()etnext()Fonction

Exemple

numbers = [1, 2, 3, 4, 5]

# Utiliser l'itérateur manuellement
iterator = iter(numbers)

print(next(iterator))  # Sortie : 1
print(next(iterator))  # Sortie : 2
print(next(iterator))  # Sortie : 3

enumerate()Fonction

Exemple

fruits = ['pomme', 'banane', 'orange']

for index, fruit in enumerate(fruits):
    print(f"Indice {index}: {fruit}")

zip()Fonction

Exemple

names = ['Alice', 'Bob', 'Charlie']
scores = [85, 92, 78]

for name, score in zip(names, scores):
    print(f"{name} score: {score}")

Itérateur vs Itérable

Il est important de comprendre la différence entre ces deux concepts :

Fonctionnalité Itérable Iterator
Définition Implémente__iter__()l'objet de la méthode Implémente__iter__()et__next__()l'objet de la méthode
Objectif Peut être itéré Effectue réellement l'opération d'itération
State Généralement sans état Maintient l’état d’itération (position actuelle, etc.)
Exemple Listes, tuples, dictionnaires, chaînes iter()L’objet retourné

Diagramme de relations

Exemple

graph TD
A[Iterable] -->|appelle iter| B[Iterator]
B -->|appelle next de façon répétée| C[Élément par élément]
C --> D[Jusqu’à StopIteration]

Bonnes pratiques et erreurs courantes

Bonnes pratiques

  1. Utilisez des générateurs pour simplifier le code

Exemple

# Recommandé : utilisez un générateur
def countdown(n):
    while n > 0:
        yield n
        n -= 1

# Non recommandé : implémenter manuellement une classe d'itérateur
  1. Tirer parti des fonctions intégrées

Exemple

# Recommandé
squares = (x*x for x in range(10))  # Expression génératrice

# Non recommandé
class SquareIterator:
    # ... implémentation verbeuse

Erreurs courantes

Erreur 1 : confondre les itérateurs et les itérables

Exemple

numbers = [1, 2, 3]

# Erreur : la liste elle-même n'est pas un itérateur
try:
    next(numbers)  # TypeError : l'objet 'list' n'est pas un itérateur
except TypeError as e:
    print(f"Erreur: {e}")

# Correct : obtenir d'abord l'itérateur
iterator = iter(numbers)
print(next(iterator))  # Sortie : 1

Erreur 2 : Continuer à utiliser un itérateur épuisé

Exemple

numbers = [1, 2, 3]
iterator = iter(numbers)

print(list(iterator))  # Sortie : [1, 2, 3]
print(list(iterator))  # Sortie : [] - itérateur épuisé !

Exercices pratiques

Exercice 1 : Créer un itérateur personnalisé

Créer unCountdownclasse qui implémente un itérateur comptant à rebours depuis un nombre spécifié jusqu'à 1 :

Exemple

class Countdown:
    def __init__(self, start):
        self.start = start
   
    def __iter__(self):
        # Votre code ici
        current = self.start
        while current > 0:
            yield current
            current -= 1

# Testez votre implémentation
for num in Countdown(5):
    print(num)  # Sortie attendue : 5, 4, 3, 2, 1

Exercice 2 : Itérateur de lignes de fichier

Créez un itérateur qui lit un fichier ligne par ligne et ajoute un numéro de ligne avant chaque ligne :

Exemple

class NumberedLines:
    def __init__(self, filename):
        self.filename = filename
   
    def __iter__(self):
        with open(self.filename, 'r', encoding='utf-8') as file:
            for line_num, line in enumerate(file, 1):
                yield f"{line_num}: {line.rstrip()}"

Résumé

Le modèle d'itérateur est un concept extrêmement important en programmation Python ; il nous permet de :

  • Accès uniforme: parcourir différentes structures de données de la même manière
  • Encapsulation de l'implémentation: masquer la structure interne des collections
  • Évaluation paresseuse: générer des données uniquement lorsque c'est nécessaire, ce qui permet d'économiser de la mémoire
  • Composabilité: fonctionne parfaitement avec des fonctionnalités telles que les générateurs et les compréhensions

Rappelez-vous ce principe simple :Tout objet qui implémente__iter__()la méthode est itérable, et tout objet qui implémente__next__()la méthode est un itérateur.

Autres extensions