3 votes

"yield" en Python

J'ai une fonction appelée x qui produit un générateur comme celui-ci :

a = 5
def x():
    global a
    if a == 3:
        raise Exception("Stop")
    a = a - 1
    yield a

Ensuite, dans le shell python, j'appelle cette fonction comme ceci :

>>> print x().next()
>>> 4
>>> print x().next()
>>> 3
>>> print x().next()
>>> <python-input-112-f3c02bba26c8> in x()
          2     global a
          3     if a == 3:
    ----> 4         raise Exception
          5     a = a - 1
          6     yield a

    Exception:

Cependant, lorsque j'appelle cette fonction et que je l'affecte à une variable, elle se comporte différemment :

>>> a = 5
>>> b = x()
>>> print b.next()
>>> 4
>>> print b.next()
>>> ----> 1 b.next()
    StopIteration:

Comment est-ce possible ? Ne devrait-il pas s'imprimer 3 et déclencher StopIteration lors de l'itération suivante ?

PS : Je le sais lorsque j'appelle la fonction pour la première fois, le corps ne fonctionne pas, il produit juste un générateur . Ce que je n'ai pas compris, c'est ce qui change si je l'appelle et l'assigne à une variable.

13voto

Martijn Pieters Points 271458

Dans votre premier exemple, vous avez créé un nouveau à chaque fois :

x().next()

Cela permet de démarrer le générateur depuis le sommet donc la première déclaration. Lorsque a == 3 l'exception est levée, sinon le générateur produit un résultat et fait une pause .

Lorsque vous attribuerez votre générateur par la suite, le système global a a commencé à 5 Le code est alors le suivant suite à partir de l'endroit où il s'est arrêté jusqu'à ce qu'il se termine ou qu'il rencontre une autre personne. yield alors terminé . Lorsqu'une fonction génératrice se termine, elle lève StopIteration .

Nous allons décomposer cela en plusieurs étapes :

  1. a = 5 .

  2. Vous créez un nouveau générateur et appelez .next() sur celui-ci. Le code suivant est exécuté :

    global a
    if a == 3:  # False
        raise Exception("Stop")
    a = a - 1   # a is now 4
    yield a

    Le générateur est mis en pause sur la dernière ligne, et 4 est obtenue.

  3. Vous créez un nouveau générateur et appelez .next() sur celui-ci. a est 4 au début :

    global a
    if a == 3:  # False
        raise Exception("Stop")
    a = a - 1   # a is now 3
    yield a

    Le générateur est mis en pause sur la dernière ligne, et 3 est obtenue.

  4. Vous créez un nouveau générateur et appelez .next() sur celui-ci. a est 3 au début :

    global a
    if a == 3:  # True
        raise Exception("Stop")

    Une exception est levée.

  5. Vous définissez a = 5 encore une fois.

  6. Vous créez un nouveau générateur, vous stockez une référence dans le fichier b et appeler .next() sur celui-ci. Le code suivant est exécuté :

    global a
    if a == 3:  # False
        raise Exception("Stop")
    a = a - 1   # a is now 4
    yield a

    Le générateur est mis en pause sur la dernière ligne, et 4 est obtenue.

  7. Vous appelez .next() à nouveau sur le même générateur existant référencé par b . Le code CV au point de pause.

    La fonction n'a plus de code à ce moment-là et retourne. StopIteration est levé.

Si vous deviez utiliser un boucle au lieu de cela, vous verriez mieux la différence :

>>> def looping(stop):
...    for i in looping(stop):
...        yield i
...
>>> looping(3).next()
0
>>> looping(3).next()
0

Notez qu'à chaque fois que je crée un nouveau générateur, la boucle recommence au début. Enregistrez une référence cependant, et vous remarquerez qu'elle continue à la place :

>>> stored = looping(3)
>>> stored.next()
0
>>> stored.next()
1
>>> stored.next()
2
>>> stored.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Pendant la boucle, chaque fois que le yield est exécutée, le code est en pause ; appel .next() reprend la fonction là où elle s'est arrêtée la fois précédente.

En StopIteration L'exception est tout à fait normale ; c'est la façon dont les générateurs communiquent qu'ils ont terminé. A for recherche cette exception pour mettre fin à la boucle :

>>> for i in looping(3):
...     print i
... 
0
1
2

0voto

paj28 Points 845

Vous n'avez pas bien compris comment fonctionne le rendement. Je pense que cet exemple peut vous aider :

>>> def a():
...    for x in range(5):
...        yield x
...
>>> a()
<generator object a at 0xb7f0a9b4>
>>> list(a())
[0, 1, 2, 3, 4]

Vous voulez normalement utiliser yield à l'intérieur d'une boucle, et il a le comportement très distinct de renvoyer une valeur, puis de reprendre la boucle plus tard.

Dans votre exemple, x renvoie toujours un générateur qui ne produira qu'un seul élément. Dans votre premier exemple, vous appelez x plusieurs fois, vous obtenez donc plusieurs résultats. Dans votre deuxième exemple, où vous l'affectez à une variable, vous ne l'appelez qu'une seule fois, et vous n'obtenez donc qu'un seul résultat.

En outre, vous ne devriez généralement pas utiliser une variable globale de la manière dont vous l'avez fait.

Paul

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X