Accéder au contenu principal

Tutoriel Python sur les itérateurs et les générateurs

Explorez la différence entre les itérateurs et les générateurs de Python et apprenez quels sont les meilleurs à utiliser dans diverses situations.
Actualisé 14 nov. 2024  · 10 min de lecture

Les itérateurs sont des objets sur lesquels on peut itérer. Ils servent de caractéristique commune au langage de programmation Python, bien rangés pour les boucles et les compréhensions de listes. Tout objet pouvant dériver un itérateur est appelé itérable. 

La construction d'un itérateur nécessite beaucoup de travail. Par exemple, l'implémentation de chaque objet itérateur doit consister en une méthode __iter__() et __next__() . Outre les conditions préalables susmentionnées, l'implémentation doit également permettre de suivre l'état interne de l'objet et de lever une exception StopIteration lorsque plus aucune valeur ne peut être renvoyée. Ces règles sont connues sous le nom de protocole de l'itérateur

La mise en œuvre de votre propre itérateur est un processus fastidieux, qui n'est nécessaire que dans certains cas. Une alternative plus simple consiste à utiliser un objet générateur. Les générateurs sont un type spécial de fonction qui utilise le mot-clé yield pour renvoyer un itérateur qui peut être parcouru, une valeur à la fois. 

La capacité à discerner les scénarios appropriés pour mettre en œuvre un itérateur ou utiliser un générateur améliorera vos compétences en tant que programmeur Python. Dans la suite de ce tutoriel, nous mettrons l'accent sur les distinctions entre les deux objets, ce qui vous aidera à choisir celui qui convient le mieux à votre situation. 

Glossaire

Durée

Définition

Itérable 

Un objet Python qui peut être parcouru en boucle ou itéré dans une boucle. Les listes, les ensembles, les tuples, les dictionnaires, les chaînes de caractères, etc. sont des exemples d'itérables. 

Itérateur

Un itérateur est un objet sur lequel on peut itérer. Les itérateurs contiennent donc un nombre dénombrable de valeurs. 

Générateur

Un type spécial de fonction qui ne renvoie pas une seule valeur : elle renvoie un objet itérateur avec une séquence de valeurs.

Évaluation paresseuse 

Stratégie d'évaluation selon laquelle certains objets ne sont produits qu'en cas de besoin. C'est pourquoi certains cercles de développeurs font également référence à l'évaluation paresseuse comme "call-by-need" (appel par besoin).

Protocole de l'itérateur 

Ensemble de règles à respecter pour définir un itérateur en Python. 

suivant()

Fonction intégrée utilisée pour renvoyer l'élément suivant d'un itérateur. 

iter()

Fonction intégrée utilisée pour convertir un itérable en itérateur. 

yield()

Mot-clé de Python similaire au mot-clé return, sauf que yield renvoie un objet générateur au lieu d'une valeur. 

Iterators et Iterables en Python

Les itérables sont des objets capables de renvoyer leurs membres un par un - ils peuvent être itérés. Les structures de données intégrées populaires de Python, telles que les listes, les tuples et les ensembles, sont considérées comme des itérables. D'autres structures de données telles que les chaînes et les dictionnaires sont également considérées comme des itérables : une chaîne peut produire une itération de ses caractères, et les clés d'un dictionnaire peuvent être itérées. En règle générale, tout objet sur lequel on peut itérer dans une boucle for est considéré comme un objet itérable. 

Explorer les itérables de Python à l'aide d'exemples

Compte tenu des définitions, nous pouvons conclure que tous les itérateurs sont également itérables. Cependant, tout itérable n'est pas nécessairement un itérateur. Un itérable ne produit un itérateur qu'une fois qu'il est itéré.

Pour démontrer cette fonctionnalité, nous allons instancier une liste, qui est un itérable, et produire un itérateur en appelant la fonction intégrée iter() sur la liste. 

list_instance = [1, 2, 3, 4]
print(iter(list_instance))

"""
<list_iterator object at 0x7fd946309e90>
"""

Bien que la liste en elle-même ne soit pas un itérateur, l'appel à la fonction iter() la convertit en itérateur et renvoie l'objet itérateur.

Pour démontrer que tous les itérables ne sont pas des itérateurs, nous allons instancier le même objet liste et tenter d'appeler la fonction next(), qui est utilisée pour renvoyer l'élément suivant dans un itérateur.  

list_instance = [1, 2, 3, 4]
print(next(list_instance))
"""
--------------------------------------------------------------------
TypeError                         Traceback (most recent call last)
<ipython-input-2-0cb076ed2d65> in <module>()
    3 print(iter(list_instance))
    4
----> 5 print(next(list_instance))
TypeError: 'list' object is not an iterator
"""

Dans le code ci-dessus, vous pouvez voir que la tentative d'appel de la fonction next() sur la liste a soulevé une TypeError - en savoir plus sur la gestion des exceptions et des erreurs en Python. Ce comportement est dû au simple fait qu'un objet liste est un itérable et non un itérateur. 

Explorer les itérateurs Python à l'aide d'exemples

Ainsi, si l'objectif est d'itérer sur une liste, un objet itérateur doit d'abord être produit. Ce n'est qu'ensuite que nous pouvons gérer l'itération à travers les valeurs de la liste.

# instantiate a list object
list_instance = [1, 2, 3, 4]

# convert the list to an iterator
iterator = iter(list_instance)

# return items one at a time
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
"""
1
2
3
4
"""

Python produit automatiquement un objet itérateur chaque fois que vous tentez de boucler sur un objet itérable. 

# instantiate a list object
list_instance = [1, 2, 3, 4]

# loop through the list
for iterator in list_instance:
  print(iterator)
"""
1
2
3
4
"""

Lorsque l'exception StopIteration est levée, la boucle se termine.

Les valeurs obtenues à partir d'un itérateur ne peuvent être récupérées que de gauche à droite. Python ne dispose pas d'une fonction previous() permettant aux développeurs de revenir en arrière dans un itérateur. 

La nature paresseuse des itérateurs

Il est possible de définir plusieurs itérateurs basés sur le même objet itérable. Chaque itérateur conservera son propre état d'avancement. Ainsi, en définissant plusieurs instances d'itérateur d'un objet itérable, il est possible d'itérer jusqu'à la fin d'une instance tandis que l'autre instance reste au début.

list_instance = [1, 2, 3, 4]
iterator_a = iter(list_instance)
iterator_b = iter(list_instance)
print(f"A: {next(iterator_a)}")
print(f"A: {next(iterator_a)}")
print(f"A: {next(iterator_a)}")
print(f"A: {next(iterator_a)}")
print(f"B: {next(iterator_b)}")
"""
A: 1
A: 2
A: 3
A: 4
B: 1
"""

Remarquez que iterator_b imprime le premier élément de la série.

On peut donc dire que les itérateurs sont paresseux : lorsqu'un itérateur est créé, les éléments ne sont pas cédés tant qu'ils ne sont pas demandés. En d'autres termes, les éléments de notre instance de liste ne seront renvoyés que lorsque nous leur demanderons explicitement de l'être avec next(iter(list_instance))

Cependant, toutes les valeurs d'un itérateur peuvent être extraites en une seule fois en appelant un conteneur de structure de données itérable intégré (c'est-à-dire list(), set(), tuple()) sur l'objet itérateur pour forcer l'itérateur à générer tous ses éléments en une seule fois.

# instantiate iterable
list_instance = [1, 2, 3, 4]

# produce an iterator from an iterable
iterator = iter(list_instance)
print(list(iterator))
"""
[1, 2, 3, 4]
"""

Il n'est pas recommandé d'effectuer cette action, en particulier lorsque les éléments renvoyés par l'itérateur sont volumineux, car le traitement prendra beaucoup de temps.

Lorsqu'un fichier de données volumineux encombre la mémoire de votre machine ou que vous disposez d'une fonction dont l'état interne doit être conservé à chaque appel, mais que la création d'un itérateur n'a pas de sens compte tenu des circonstances, il est préférable d'utiliser un objet générateur.

Générateurs Python

La solution la plus rapide pour mettre en œuvre un itérateur est d'utiliser un générateur. Bien que les générateurs ressemblent à des fonctions Python ordinaires, ils sont différents. Tout d'abord, un objet générateur ne renvoie pas d'éléments. Au lieu de cela, il utilise le mot-clé yield pour générer des articles à la volée. On peut donc dire qu'un générateur est un type particulier de fonction qui tire parti de l'évaluation paresseuse.

Les générateurs ne stockent pas leur contenu en mémoire, contrairement à ce que l'on attendrait d'un itérable classique. Par exemple, si l'objectif était de trouver tous les facteurs d'un entier positif, nous mettrions typiquement en œuvre une fonction traditionnelle (apprenez-en plus sur les fonctions Python dans ce tutoriel) comme suit :  

def factors(n):
  factor_list = []
  for val in range(1, n+1):
      if n % val == 0:
          factor_list.append(val)
  return factor_list

print(factors(20))
"""
[1, 2, 4, 5, 10, 20]
"""

Le code ci-dessus renvoie la liste complète des facteurs. Notez toutefois la différence lorsqu'un générateur est utilisé à la place d'une fonction Python traditionnelle :

def factors(n):
  for val in range(1, n+1):
      if n % val == 0:
          yield val
print(factors(20))

"""
<generator object factors at 0x7fd938271350>
"""

Comme nous avons utilisé le mot-clé yield au lieu de return, la fonction n'est pas quittée après l'exécution. En substance, nous avons demandé à Python de créer un objet générateur au lieu d'une fonction traditionnelle, ce qui permet de suivre l'état de l'objet générateur. 

Par conséquent, il est possible d'appeler la fonction next() sur l'itérateur paresseux pour afficher les éléments de la série un par un. 

def factors(n):
  for val in range(1, n+1):
      if n % val == 0:
          yield val
         
factors_of_20 = factors(20)
print(next(factors_of_20))

"""
1
"""

Une autre façon de créer un générateur est de le comprendre. Les expressions génératrices adoptent une syntaxe similaire à celle de la compréhension d'une liste, sauf qu'elles utilisent des parenthèses arrondies au lieu de parenthèses carrées.

print((val for val in range(1, 20+1) if n % val == 0))
"""
<generator object <genexpr> at 0x7fd940c31e50>
"""

Explorer les possibilités de Python yield Keyword

Le mot-clé yield contrôle le flux d'une fonction de générateur. Au lieu de sortir de la fonction comme c'est le cas lorsque return est utilisé, le mot-clé yield renvoie la fonction mais garde en mémoire l'état de ses variables locales.

Le générateur renvoyé par l'appel à yield peut être assigné à une variable et itéré avec le mot-clé next() - cela exécutera la fonction jusqu'au premier mot-clé yield qu'elle rencontrera. Lorsque le mot-clé yield est frappé, l'exécution de la fonction est suspendue. Dans ce cas, l'état de la fonction est sauvegardé. Il nous est donc possible de reprendre l'exécution de la fonction à notre guise. 

La fonction se poursuit à partir de l'appel à yield. Par exemple : 

def yield_multiple_statments():
  yield "This is the first statment"
  yield "This is the second statement"  
  yield "This is the third statement"
  yield "This is the last statement. Don't call next again!"
example = yield_multiple_statments()
print(next(example))
print(next(example))
print(next(example))
print(next(example))
print(next(example))
"""
This is the first statment
This is the second statement
This is the third statement
This is the last statement. Don't call next again or else!
--------------------------------------------------------------------
StopIteration                  Traceback (most recent call last)
<ipython-input-25-4aaf9c871f91> in <module>()
    11 print(next(example))
    12 print(next(example))
---> 13 print(next(example))
StopIteration:
"""

Dans le code ci-dessus, notre générateur a quatre appels à yield, mais nous essayons d'appeler next cinq fois, ce qui soulève une exception StopIteration. Ce comportement s'explique par le fait que notre générateur n'est pas une série infinie, et que le fait de l'appeler plus de fois que prévu a épuisé le générateur.

Synthèse 

Pour résumer, les itérateurs sont des objets sur lesquels on peut itérer, et les générateurs sont des fonctions spéciales qui tirent parti de l'évaluation paresseuse. L'implémentation de votre propre itérateur signifie que vous devez créer une méthode __iter__() et __next__(), alors qu'un générateur peut être implémenté en utilisant le mot-clé yield dans une fonction ou une compréhension Python. 

Vous préférerez peut-être utiliser un itérateur personnalisé plutôt qu'un générateur si vous avez besoin d'un objet avec un comportement complexe de maintien d'état ou si vous souhaitez exposer d'autres méthodes que __next__(), __iter__(), et __init__(). D'autre part, un générateur peut être préférable lorsqu'il s'agit de traiter de grands ensembles de données car ils ne stockent pas leur contenu en mémoire ou lorsqu'il n'est pas nécessaire d'implémenter un itérateur. 

Sujets

Les meilleurs cours de Python

cours

Introduction to Functions in Python

3 hr
432.6K
Learn the art of writing your own functions in Python, as well as key concepts like scoping and error handling.
Afficher les détailsRight Arrow
Commencer le cours
Voir plusRight Arrow
Apparenté

blog

Les 32 meilleures questions d'entretien sur AWS et leurs réponses pour 2024

Un guide complet pour explorer les questions d'entretien AWS de base, intermédiaires et avancées, ainsi que des questions basées sur des situations réelles. Il couvre tous les domaines, garantissant ainsi une stratégie de préparation bien équilibrée.
Zoumana Keita 's photo

Zoumana Keita

30 min

blog

Les 20 meilleures questions d'entretien pour les flocons de neige, à tous les niveaux

Vous êtes actuellement à la recherche d'un emploi qui utilise Snowflake ? Préparez-vous à répondre à ces 20 questions d'entretien sur le flocon de neige pour décrocher le poste !
Nisha Arya Ahmed's photo

Nisha Arya Ahmed

20 min

blog

Q2 2023 DataCamp Donates Digest

DataCamp Donates a offert plus de 20k bourses d'études à nos partenaires à but non lucratif au deuxième trimestre 2023. Découvrez comment des apprenants défavorisés et assidus ont transformé ces opportunités en réussites professionnelles qui ont changé leur vie.
Nathaniel Taylor-Leach's photo

Nathaniel Taylor-Leach

blog

Célébration de Saghar Hazinyar : Une boursière de DataCamp Donates et une diplômée de Code to Inspire

Découvrez le parcours inspirant de Saghar Hazinyar, diplômée de Code to Inspire, qui a surmonté les défis en Afghanistan et s'est épanouie grâce à une bourse de DataCamp Donates.
Fereshteh Forough's photo

Fereshteh Forough

4 min

blog

2022-2023 Rapport annuel DataCamp Classrooms

À l'aube de la nouvelle année scolaire, DataCamp Classrooms est plus motivé que jamais pour démocratiser l'apprentissage des données, avec plus de 7 650 nouveaux Classrooms ajoutés au cours des 12 derniers mois.
Nathaniel Taylor-Leach's photo

Nathaniel Taylor-Leach

8 min

blog

Nous avons fait don de bourses DataCamp Premium à un million de personnes, et ce n'est pas fini.

Réparties entre nos deux programmes d'impact social, DataCamp Classrooms et #DCDonates, les bourses offrent un accès illimité à tout ce que DataCamp Premium a à offrir.
Nathaniel Taylor-Leach's photo

Nathaniel Taylor-Leach

Voir plusVoir plus