Itératifs en Python

Cliquez sur A pour faire toutes les polices sur la page plus petite.

Cliquez sur A pour faire toutes les polices sur la page plus grande.

Cliquez sur HC pour basculer en mode de contraste élevé. Lorsque vous déplacez votre souris sur certains mots en gras dans le mode de contraste élevé, les mots connexes sont automatiquement mis en surbrillance. Le texte est affiché en noir et blanc.

Iterables contre itérateurs

Dans les termes les plus larges possibles, un itérable est quelque chose que vous pouvez parcourir, et un itérateur est ce que l'interprète utilise pour faire l'itération. Cette description, cependant, est trop générale pour être de valeur suffisante. La question fondamentale est: qu'est-ce que l'interprète faire quand vous écrivez pour i à s: dans votre programme ou tout autre module?

En Python moderne, l'itération est pris en charge par deux mécanismes bien distincts. Donc, la réponse à la question « comment l'interprète itérer sur les objets? » dépend de la présence de méthodes spécifiques sur l'objet. Si l'objet a une méthode __ de __iter (), alors il est itérable en utilisant le mécanisme nouveau style itération. Sinon, l'interprète cherche une méthode __ de __getitem () et, si elle en trouve un, utilise un mécanisme l'ancien style itération. Si aucune des deux méthodes est présent, l'interprète soulève une exception TypeError parce que l'objet n'est pas itérable.

Si un objet, o. n'a pas de méthode __ de __iter () et vous dire que l'interprète itérer dessus, l'interprète initialise une méthode variable interne à zéro et appelle à plusieurs reprises le __ de __getitem de l'objet () avec des valeurs plus élevées de la successivement variable interne. Du point de vue de l'objet, il est comme si elle était manipulée par ce code:

OBSERVER: logique efficace d'une ancienne boucle

En fait, vous pouvez créer vos propres classes dont les instances peuvent être itéré de cette façon. Tout ce que vous devez faire est de fournir une méthode __ de __getitem (n) qui déclenche une exception IndexError lorsque la valeur de n est trop élevée. Supposons que vous vouliez mettre en oeuvre des séquences de longueur fixe d'objets. Vous pouvez définir une fonction pour créer une séquence appropriée (liste ou tuple ou chaîne) avec le nombre de composants dans ce (si fls ( « * », 12) retourneraient « ************ ". par exemple).

Sinon, vous pouvez définir une classe fls, dont la méthode __init __ () a la même signature que la fonction ci-dessus. Créer un nouveau projet Python3_Lesson03 et l'affecter à vos Python3_Lessons jeu de travail. Ensuite, créez fls.py dans le projet Python3_Lesson03 comme indiqué.

CODE DE TYPE: fls.py

OBSERVER: sortie de l'exécution de la classe fls

Ainsi, à des fins d'itération, vous pouvez voir que les objets fls semblent agir comme d'autres séquences, seulement avec un comportement très ennuyeux parce que tous les éléments sont contraints d'être la même la seule valeur que __getitem __ () ne retourne jamais est celle qui a été adoptée pour __init __ (). Mais le point principal est que vous savez un peu plus sur le mécanisme d'itération de Python. Maintenant, essayez quelques autres cas pour vous-même utiliser une session de console interactive pour créer et tester des objets fls plus interactive.

Rappelez-vous, vous devrez importer la classe fls à partir du module fls afin d'être en mesure de créer des instances de celui-ci.

Le mécanisme d'itération décrit ci-dessus est très bien quand vous itérez articles sur énumérés dans leur séquence, mais il ne va pas naturellement à des collections comme des ensembles et dicts, qui ne précisent pas un ordre naturel pour leurs articles. Dicts, en fait, ont une méthode __ de __getitem (), mais il prend une valeur clé et renvoie l'élément approprié (en supposant qu'une clé à cette valeur existe, s'il n'y a pas une telle clé, elle soulève une exception KeyError). Ensembles ne sont même pas une méthode __ de __getitem (), car ils sont effectivement « dicts item-less ».

Il a été de surmonter les problèmes comme celui-ci que le protocole d'itération « nouveau style » a été défini. Vous avez appris ci-dessus que l'interprète recherchera une méthode __ de __iter () sur les objets que vous itérer sur. Si elle trouve __ de __iter (). il l'utilise pour créer un itérateur de la itérables vous itérez.

Le iterator aura une méthode ce __ de __next () est une exigence du protocole d'itération. Chaque fois autour de la boucle, l'interprète obtient la valeur suivante pour la méthode itérables en appelant __next du iterator __ (). Encore une fois, vous pouvez peut-être comprendre plus facilement avec un équivalent Python approximative pour une boucle sur un nouveau style itérables:

OBSERVER: logique efficace d'un nouveau style pour la boucle

CODE DE TYPE: Entrez les informations suivantes dans une session d'interpréteur interactif

Quelle est la différence entre la première boucle et la seconde? Regardez la liste dir () de LST, qui est une instance de liste, et notez qu'il a à la fois des méthodes __ de __iter () et __getitem __ (). Lorsque le itère interprète sur la liste, appelant sa méthode __ de __iter () crée une nouvelle iterator, ce qui donne une séquence complète des valeurs, à chaque fois qu'il rencontre une boucle.

Nous avons ensuite créé une liste objet itérateur en appelant manuellement notre méthode __iter de liste de __ (). Notez que la liste objet iterator a également une méthode __ de __iter (), et ajoute une méthode __next __ (), mais il manque un __getitem __ (). Le procédé de __iter __ () de l'itérateur est assez différente de celle de la liste, cependant:

OBSERVER: Méthode __iter __ () - en Python se lise comme suit:

En d'autres termes, chaque fois que vous itérer sur une liste (qui est un itérable), l'appel à la méthode __ de __iter () crée une nouvelle iterator, qui a son propre Etat indépendant. La méthode __iter de iterator __ (), cependant, ne crée pas une nouvelle iterator, ce qui signifie que les boucles internes et externes partagent le même iterator. Cela signifie que lorsque la boucle extérieure tente de commencer sa deuxième itération, le iterator a déjà été épuisé par la boucle intérieure et (pour la deuxième fois) soulève l'exception StopIteration.

Créer vos propres itérateurs

Plutôt que de créer un exemple maintenant, nous allons créer dans la section suivante. Tout d'abord, nous allons créer un générateur, puis nous allons construire un itérateur équivalent.

Générateurs: Éviter la création de grandes séquences

Le protocole d'itération discuté ci-dessus est également en jeu avec ce qu'on appelle les fonctions de générateur. La seule différence apparente entre une fonction de générateur et le type régulier, vous avez traité avant est l'apparition du mot-clé yield dans le corps de la fonction. Alors, quelle est la différence entre une fonction régulière et une fonction de générateur?

La réponse est que l'appel d'une fonction de générateur produit un type spécial d'objet iterator (un « générateur »). L'espace de noms de fonction est créé et initialisé avec les valeurs de l'argument. Le code de fonction commence seulement avec l'exécution du premier appel à la méthode __ () de __next du générateur. L'exécution se poursuit jusqu'à ce qu'une expression d'élasticité est évaluée: la valeur de l'expression suivante rendement devient la valeur de la méthode de __next __ () appel. Vous pouvez voir cela avec une fonction de générateur très simple dans une session interactive.

CODE DE TYPE: Entrez le code suivant dans une fenêtre d'interpréteur interactif

g () est une fonction de générateur, bien que lorsque vous demandez à l'interprète à ce sujet que vous ne voyez pas de différence par rapport à toute autre fonction. Appelant crée un objet générateur, cependant, et la liste dir () montre qu'il a les méthodes nécessaires pour un itérateur. Appelant la méthode __ () de __next de l'objet renvoie le résultat de l'expression suivante de rendement dans le corps de code de la fonction.

Si la fonction se termine avant de rencontrer une expression de rendement (soit en exécutant une instruction de retour ou de faire tomber du fond), la méthode __ de __next () appel soulève une exception StopIteration comme tout autre itérateur. A noter également que, une fois que le générateur commence à soulever des exceptions StopIteration lorsque __next __ () est appelée, elle continue de le faire pour chaque appel l'itérateur suivant est épuisé.

Avantages des fonctions Générateur

La chose vraiment pratique sur les fonctions du générateur est qu'ils vous permettent d'effectuer toutes sortes de calculs complexes pour produire les valeurs dans une séquence, mais le code qui consomme (fait usage de) ces valeurs peuvent être entièrement séparés du générateur qui les produit. Les valeurs sont consommées dans une simple boucle ou tout autre contexte similaire itérative en Python, comme une compréhension de liste.

Non seulement ils font votre code plus simple en séparant la production et la consommation de séquences, mais générateurs vous permettent de créer des valeurs séquence un à la fois, comme ils sont consommés. Il n'y a pas besoin de construire une liste ou tuple de les stocker dans, ce qui signifie que vos programmes utiliseront moins de stockage et de fonctionner plus rapidement (bien que ces avantages ne font pas vraiment beaucoup de différence à moins que le nombre d'objets est grand).

Un générateur simple fonction

Supposons que vous devez produire des séquences déterminées par une liste, mais il faut répéter le premier élément de la liste une fois, la deuxième fois, et ainsi de suite. Donc, étant donné une liste [2, 4, 6]. la séquence résultante serait de 2, 4, 4, 6, 6, 6. Écrivons un générateur qui produit de telles séquences. Mais d'abord, nous allons écrire des tests pour faire en sorte que notre fonction fonctionne générateur. Créer testgen.py dans votre dossier Python3_Lesson03 / src comme indiqué:

CODE DE TYPE: testgen.py

Comme d'habitude, nous commençons avec une simple fonction de talon pour vous assurer que les tests échouent. Maintenant, créez gen123.py dans votre dossier Python3_Lesson03 / src comme indiqué:

CODE DE TYPE: gen123.py

Un équivalent Iterator du générateur

Comme vous l'avez appris ci-dessus, il est également possible d'écrire des classes qui obéissent le protocole d'itération. Vous terminerez cette leçon en écrivant un équivalent iterator de la fonction de générateur ci-dessus. Puisque vous le souhaitez effectuer exactement les mêmes que les générateurs de gen123, vous pouvez utiliser les mêmes tests pour vérifier son fonctionnement qui est l'un des avantages d'un environnement piloté par les tests! Le nouveau composant devrait idéalement être un « remplacement drop-in » pour la fonction de générateur. Créer class123.py dans votre dossier Python3_Lesson03 / src comme indiqué:

CODE DE TYPE: class123.py

Ce code est beaucoup plus complexe. Cela ne devrait pas être surprenant, car les fonctions de générateur ont été conçus pour résoudre ce type de problème proprement et simplement.

Au lieu d'appeler la fonction de générateur, la routine de test va maintenant appeler la classe de votre iterator (qui, vous remarquerez, a le même nom). Cela provoque sa méthode __init __ () pour être exécuté, et la liste des valeurs est stocké comme une variable d'instance. sont initialisés deux autres variables d'instance: l'un pour garder une trace dont l'article est en cours de sortie, et l'autre de garder une trace de combien de fois la valeur actuelle a été produite.

Toute la magie, bien sûr, se déroule dans la méthode __ de __next (). D'abord, il vérifie s'il est temps de passer à l'élément suivant de la liste de valeurs (le numéro de l'article et le nombre sont mis en place au départ pour faire en sorte que cette branche est actionnée sur le premier appel). Dans ce cas, la variable d'instance val est récupérée.

Si aucune plus de valeurs sont disponibles, la méthode soulève une exception StopIteration de mettre fin à la boucle. Notez bien que cette action peut être répétée une fois-la méthode commence à soulever l'exception, il devrait être relevé pour chaque appel ultérieur.

Une fois que la valeur de l'élément correct est établie, le compte est incrémenté et la valeur est renvoyée à la suite de l'appel.

Test du module est facile. Il suffit de faire les modifications suivantes au programme de test:

CODE DE EDIT: testgen.py

Après le protocole d'itération nouveau style a été adopté en Python, l'un des développeurs fait observer qu'il serait très utile de pouvoir écrire des expressions qui étaient semblables à la liste compréhensions à l'aide de l'itération (pour) et la sélection (si) des éléments pour produire des expressions qui a généré leurs résultats plutôt que de produire une liste. Le raisonnement derrière cela est la même chose que le raisonnement derrière standards générateurs créant les objets un par un « à la demande » est plus efficace l'espace, et est susceptible d'accélérer les programmes traitant de grandes séquences considérablement, ainsi que la réduction de leur mémoire exigences.

La syntaxe d'une expression du générateur est la même que pour la liste (compréhensions appris dans un cours plus tôt), mais avec des parenthèses au lieu des crochets. Parce qu'ils sont des générateurs, cependant, vous ne voyez que les valeurs individuelles lorsque vous les consommez dans une itération. En savoir un peu plus sur eux en jouant dans une session d'interpréteur interactif.

Notez que les expressions du générateur sont itérateurs, mais pas iterables: une fois que vous avez itéré eux, ils sont épuisés, et toute nouvelle tentative d'itérer sur l'expression soulève une StopIteration immédiate. Observez également que vous avez utilisé une nouvelle fonction intégrée dans cette session. L'appel suivant (o) est à peu près équivalent à appeler o .__ suivant __ (). jusqu'à la levée d'une exécution StopIteration lorsqu'il n'y a pas plus de valeurs sont disponibles.

Vous savez maintenant beaucoup plus sur les itère Python chemin sur les objets que vous avez fait auparavant. Avec de la chance, cette connaissance vous permettra de construire des objets qui vous aident à résoudre plus efficacement vos problèmes.

Lorsque vous avez terminé la leçon, revenir au programme et compléter les jeux-questionnaires et des projets.

Articles Liés