web-dev-qa-db-fra.com

__next__ dans les générateurs et les itérateurs et qu'est-ce qu'une méthode-wrapper?

Je lisais sur générateur et les itérateurs et le rôle de __next__()

'__next__' in dir(mygen). est vrai

'__next__' in dir(mylist), est false

Alors que je regardais plus profondément dedans, 

'__next__' in dir (mylist.__iter__()) est true

  1. pourquoi le__next__ est-il uniquement disponible pour la liste mais uniquement pour __iter__() et mygen mais pas mylist. Comment __iter__() appelle-t-il __next__ lorsque nous parcourons la liste en utilisant list-comprehension

    En essayant de monter manuellement le générateur (+1), j'ai appelé mygen.__next__(). Ça n'existe pas. Il n'existe que sous la forme mygen.__next__ qui s'appelle method-wrapper.

  2. qu'est-ce qu'une méthode-wrapper et que fait-elle? Comment est-il appliqué ici: dans mygen() and __iter__() ?

  3. si __next__ est ce que le générateur et l'itérateur fournissent (et leurs propriétés uniques), alors quelle est la différence entre le générateur et l'itérateur? *

    Réponse à la question 3: résolu, comme indiqué par le mod/éditeur:

    Différence entre les générateurs et les itérateurs de Python

UPDATE: le générateur et l'itérateur ont __next__(). Mon erreur. En regardant les journaux, le test mygen.__next__() me donnait en quelque sorte une erreur d’exception Stopiteration. Mais je n'ai pas pu reproduire cette erreur à nouveau.

Merci à tous pour votre réponse!

6
theMobDog

Les méthodes spéciales __iter__ et __next__ font partie du protocole itérateur permettant de créer types itérateur . Pour cela, vous devez différencier deux choses distinctes: Iterables et iterators.

Iterables sont des choses qui peuvent être itérées, généralement, ce sont des sortes d'éléments conteneurs qui contiennent des éléments. Des exemples courants sont des listes, des tuples ou des dictionnaires.

Pour itérer un itérable, vous utilisez un itérateur. Un itérateur est l'objet qui vous aide à parcourir le conteneur. Par exemple, lors de l'itération d'une liste, l'itérateur conserve essentiellement le suivi de l'index auquel vous vous trouvez actuellement.

Pour obtenir un itérateur, la méthode __iter__ est appelée sur l'itérable. Cela ressemble à une méthode fabrique qui renvoie un nouvel itérateur pour cet itérable spécifique. Un type ayant une méthode __iter__ définie le transforme en itérable.

L'itérateur a généralement besoin d'une seule méthode, __next__, qui renvoie l'élément suivant pour l'itération. De plus, pour rendre le protocole plus facile à utiliser, chaque itérateur doit également être un itératif, en se retournant dans la méthode __iter__.

À titre d’exemple rapide, il s’agirait d’une implémentation possible d’itérateur pour une liste:

class ListIterator:
    def __init__ (self, lst):
        self.lst = lst
        self.idx = 0

    def __iter__ (self):
        return self

    def __next__ (self):
        try:
            item = self.lst[self.idx]
        except IndexError:
            raise StopIteration()
        self.idx += 1
        return item

L'implémentation de la liste pourrait alors simplement renvoyer ListIterator(self) à partir de la méthode __iter__. Bien sûr, l'implémentation réelle des listes se fait en C, cela semble donc un peu différent. Mais l'idée est la même.

Les itérateurs sont utilisés de manière invisible à divers endroits en Python. Par exemple, une boucle for:

for item in lst:
    print(item)

C'est un peu la même chose pour ce qui suit:

lst_iterator = iter(lst) # this just calls `lst.__iter__()`
while True:
    try:
        item = next(lst_iterator) # lst_iterator.__next__()
    except StopIteration:
        break
    else:
        print(item)

Donc, la boucle for demande un itérateur à partir de l'objet itérable, puis appelle __next__ sur cette ère jusqu'à ce qu'il rencontre l'exception StopIteration. Si cela se produit sous la surface, vous voudrez également que les itérateurs implémentent le __iter__: sinon, vous ne pourriez jamais passer en boucle sur un itérateur.


En ce qui concerne les générateurs, ce que les gens appellent généralement est en fait un générateur fonction, c'est-à-dire une définition de fonction qui a des instructions yield. Une fois que vous appelez cette fonction de générateur, vous récupérez un générateur. Un générateur est essentiellement un itérateur, même s’il est élégant (puisqu’il fait plus que se déplacer dans un conteneur). En tant qu'itérateur, il dispose d'une méthode __next__ pour «générer» l'élément suivant et d'une méthode __iter__ pour se renvoyer.


Un exemple de fonction de générateur serait le suivant:

def exampleGenerator():
    yield 1
    print('After 1')
    yield 2
    print('After 2')

Le corps de la fonction contenant une instruction yield le transforme en une fonction génératrice. Cela signifie que lorsque vous appelez exampleGenerator(), vous récupérez un objet générateur. Les objets Generator implémentent le protocole itérateur, nous pouvons donc appeler __next__ dessus (ou utiliser la fonction next() comme ci-dessus):

>>> x = exampleGenerator()
>>> next(x)
1
>>> next(x)
After 1
2
>>> next(x)
After 2
Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    next(x)
StopIteration

Notez que le premier appel next() n'a encore rien imprimé. C’est la particularité des générateurs: ils sont paresseux et n’évaluent que ce qui est nécessaire pour obtenir le prochain élément de l’itérable. Seulement avec le deuxième appel next(), nous obtenons la première ligne imprimée à partir du corps de la fonction. Et nous avons besoin d’un autre appel next() pour épuiser l’itérable (car aucune autre valeur n’a été cédée).

Mais en dehors de cette paresse, les générateurs agissent simplement comme des iterables. Vous obtenez même une exception StopIteration à la fin, qui permet aux générateurs (et fonctions de générateur) d'être utilisés en tant que sources de boucle for et partout où des itérables «normaux» peuvent être utilisés.

Le gros avantage des générateurs et de leur paresse est la possibilité de générer des choses à la demande. Une bonne analogie pour cela est le défilement sans fin sur les sites Web: vous pouvez faire défiler élément après (appelant next() sur le générateur), et de temps en temps, le site Web doit interroger un serveur afin de récupérer plus d'éléments à parcourir . Idéalement, cela se produit sans que vous ne le remarquiez. Et c’est exactement ce que fait un générateur. Cela permet même des choses comme ceci:

def counter():
    x = 0
    while True:
        x += 1
        yield x

Non paresseux, cela serait impossible à calculer puisqu'il s'agit d'une boucle infinie. Mais paresseusement, en tant que générateur, il est possible de consommer cet élément itératif après un élément. Au départ, je voulais vous éviter d’implémenter ce générateur en tant que type d’itérateur entièrement personnalisé, mais dans ce cas, ce n’est pas vraiment difficile, alors voici:

class CounterGenerator:
    def __init__ (self):
        self.x = 0

    def __iter__ (self):
        return self

    def __next__ (self):
        self.x += 1
        return self.x
20
poke

Pourquoi __next__ est-il uniquement disponible pour la liste mais uniquement pour __iter__() et mygen mais pas mylist. Comment __iter__() appelle-t-il __next__ lorsque nous parcourons la liste en utilisant list-comprehension.

Étant donné que les listes ont un objet séparé renvoyé par iter pour gérer l'itération, cet objet __iter__ est appelé de manière consécutive. 

Donc, pour les listes:

iter(l) is l # False, returns <list-iterator object at..>

Alors que, pour les générateurs:

iter(g) is g # True, its the same object

Dans les constructions en boucle, iter va d'abord être appelé sur l'objet cible à boucler. iter appelle __iter__ et un itérateur doit être renvoyé; son __next__ est appelé jusqu'à ce qu'il ne reste plus d'éléments disponibles.

Qu'est-ce qu'une méthode-wrapper et que fait-elle? Comment est-il appliqué ici: dans mygen() et __iter__()?

Si je ne me trompe pas, un wrapper de méthode est une méthode implémentée dans C. Quel est ce que ces deux iter(list).__iter__ (list est un objet implémenté dans C) et gen.__iter__ (pas sûr ici, mais les générateurs sont probablement aussi) sont.

Si __next__ est ce que le générateur et l'itérateur fournissent (et leurs propriétés uniques), quelle est la différence entre le générateur et l'itérateur?

Un générateur est un itérateur, tout comme l'itérateur fourni par iter(l). C'est un itérateur puisqu'il fournit une méthode __next__ (qui, généralement, lorsqu'il est utilisé dans une boucle for, il est capable de fournir des valeurs jusqu'à épuisement).

2

__next__ et __iter__ sont des wrappers de méthode pour quand vous faites next(some_gen) ou iter(some_sequence). next(some_gen) est identique à some_gen.__next__()

Donc, si je fais mygen = iter(mylist) alors mygen est mylist implémenté en tant qu'objet générateur et possède un descripteur de méthode __next__. Les listes elles-mêmes n'ont pas cette méthode car elles ne sont pas des générateurs.

Les générateurs sont des itérateurs. Départ Différence entre les générateurs et les itérateurs

1
sytech