Comment implémenter l'itération dans un objet Python personnalisé

PythonPythonBeginner
Pratiquer maintenant

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

Introduction

Les mécanismes d'itération intégrés à Python, tels que les boucles for et les compréhensions de liste, sont des outils puissants pour travailler avec des collections de données. Cependant, que se passe-t-il si vous voulez créer vos propres objets personnalisés qui peuvent être parcourus? Dans ce tutoriel, nous allons explorer la manière de mettre en œuvre l'itération dans un objet Python personnalisé, vous permettant de tirer pleinement parti des capacités itératives de Python.

Comprendre l'itération en Python

Qu'est-ce que l'itération?

En programmation, l'itération fait référence au processus d'exécution répétée d'un ensemble d'instructions ou d'un bloc de code. En Python, l'itération est un concept fondamental qui vous permet de travailler avec des séquences, telles que les listes, les tuples et les chaînes de caractères, ainsi que d'autres objets itérables.

Objets itérables

Un objet itérable est un objet qui peut être parcouru, ce qui signifie qu'il peut être parcouru en boucle et que ses éléments peuvent être accédés un par un. En Python, les objets itérables courants incluent :

  • Les listes
  • Les tuples
  • Les chaînes de caractères
  • Les dictionnaires
  • Les ensembles
  • Les fichiers
  • Les objets personnalisés qui implémentent le protocole d'itérateur

La boucle for

La boucle for est la manière la plus courante de parcourir un objet itérable en Python. La boucle for vous permet d'exécuter un bloc de code pour chaque élément de l'objet itérable.

fruits = ['apple', 'banana', 'cherry']
for fruit in fruits:
    print(fruit)

Sortie :

apple
banana
cherry

La boucle while

La boucle while est une autre manière d'implémenter l'itération en Python. La boucle while continue d'exécuter un bloc de code tant qu'une condition spécifiée est vraie.

count = 0
while count < 5:
    print(count)
    count += 1

Sortie :

0
1
2
3
4

Itérateurs et protocole d'itérateur

En coulisse, la boucle for et d'autres mécanismes d'itération en Python utilisent le protocole d'itérateur. Un itérateur est un objet qui implémente le protocole d'itérateur, qui définit deux méthodes : __iter__() et __next__(). La méthode __iter__() renvoie l'objet itérateur lui-même, et la méthode __next__() renvoie l'élément suivant dans la séquence.

class MyIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index < len(self.data):
            result = self.data[self.index]
            self.index += 1
            return result
        else:
            raise StopIteration()

my_iterator = MyIterator([1, 2, 3, 4, 5])
for item in my_iterator:
    print(item)

Sortie :

1
2
3
4
5

Implémentation d'itérateurs personnalisés

Le protocole d'itérateur

Comme mentionné précédemment, le protocole d'itérateur en Python définit deux méthodes : __iter__() et __next__(). Pour créer un itérateur personnalisé, vous devez implémenter ces deux méthodes dans votre propre classe.

Implémentation de la méthode __iter__()

La méthode __iter__() devrait renvoyer l'objet itérateur lui-même. Cette méthode est appelée lorsque vous utilisez la fonction iter() ou lorsque vous utilisez l'objet dans une boucle for.

class MyIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        return self

Implémentation de la méthode __next__()

La méthode __next__() devrait renvoyer l'élément suivant dans la séquence. S'il n'y a plus d'éléments, elle devrait lever une exception StopIteration.

class MyIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index < len(self.data):
            result = self.data[self.index]
            self.index += 1
            return result
        else:
            raise StopIteration()

Utilisation de l'itérateur personnalisé

Une fois que vous avez implémenté le protocole d'itérateur, vous pouvez utiliser votre itérateur personnalisé dans une boucle for ou avec d'autres mécanismes d'itération.

my_iterator = MyIterator([1, 2, 3, 4, 5])
for item in my_iterator:
    print(item)

Sortie :

1
2
3
4
5

Evaluation paresseuse avec les générateurs

Les générateurs sont un type spécial de fonction qui peut être utilisé pour créer des itérateurs personnalisés. Les générateurs utilisent le mot-clé yield pour renvoyer des valeurs une par une, plutôt que de construire une liste complète en mémoire.

def my_generator(n):
    i = 0
    while i < n:
        yield i
        i += 1

my_gen = my_generator(5)
for item in my_gen:
    print(item)

Sortie :

0
1
2
3
4

Les générateurs peuvent être plus efficaces en mémoire que la création d'une liste complète, en particulier lorsqu'il s'agit de travailler avec des ensembles de données volumineux ou infinis.

Application d'objets personnalisés itératifs

Cas d'utilisation d'itérateurs personnalisés

Les itérateurs personnalisés peuvent être utiles dans diverses situations, telles que :

  • Parcourir des ensembles de données volumineux ou infinis sans consommer trop de mémoire
  • Implémenter des structures de données personnalisées qui peuvent être parcourues
  • Fournir une manière plus intuitive ou spécifique au domaine de parcourir des données

Exemple : Parcourir un arbre de recherche binaire

Considérons un exemple d'arbre de recherche binaire (ARB) qui peut être parcouru à l'aide d'un itérateur personnalisé.

class Node:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None

class BinarySearchTree:
    def __init__(self):
        self.root = None

    def insert(self, value):
        ## Implémentation de la méthode insert omise pour la brièveté

    def __iter__(self):
        return BSTIterator(self.root)

class BSTIterator:
    def __init__(self, root):
        self.stack = []
        self.push_left_children(root)

    def __next__(self):
        if not self.stack:
            raise StopIteration()
        node = self.stack.pop()
        self.push_left_children(node.right)
        return node.value

    def push_left_children(self, node):
        while node:
            self.stack.append(node)
            node = node.left

## Exemple d'utilisation
bst = BinarySearchTree()
bst.insert(5)
bst.insert(3)
bst.insert(7)
bst.insert(1)
bst.insert(4)
bst.insert(6)
bst.insert(8)

for value in bst:
    print(value)

Sortie :

1
3
4
5
6
7
8

Dans cet exemple, nous avons implémenté un itérateur personnalisé pour la classe BinarySearchTree. La classe BSTIterator utilise une pile pour effectuer un parcours in-order de l'arbre de recherche binaire, nous permettant de parcourir les éléments de l'arbre dans l'ordre croissant.

Parcourir des séquences infinies

Les itérateurs personnalisés peuvent également être utilisés pour travailler avec des séquences infinies, telles que la suite de Fibonacci ou la suite des nombres premiers. En utilisant des générateurs, nous pouvons créer des itérateurs qui peuvent générer l'élément suivant dynamiquement, sans stocker l'ensemble de la séquence en mémoire.

def fibonacci_generator():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci_generator()
for i in range(10):
    print(next(fib))

Sortie :

0
1
1
2
3
5
8
13
21
34

En utilisant une fonction génératrice, nous pouvons créer un itérateur qui peut générer la suite de Fibonacci indéfiniment, sans consommer une quantité importante de mémoire.

Sommaire

À la fin de ce tutoriel, vous aurez une compréhension solide de la manière d'implémenter l'itération dans vos propres objets Python personnalisés. Vous apprendrez les bases des itérateurs personnalisés, comment les appliquer à vos objets et explorer les cas d'utilisation pratiques de cette technique puissante. Maîtriser l'itération personnalisée en Python vous permettra de créer un code plus flexible, efficace et intuitif qui s'intègre parfaitement aux fonctionnalités principales du langage.