En Python, y a-t-il une différence entre la création d'un objet générateur via une expression de générateur et l'utilisation de l' instruction yield ?
En utilisant le rendement :
def Generator(x, y):
for i in xrange(x):
for j in xrange(y):
yield(i, j)
Utilisation de l' expression du générateur :
def Generator(x, y):
return ((i, j) for i in xrange(x) for j in xrange(y))
Les deux fonctions renvoient des objets générateurs, qui produisent des tuples, par exemple (0,0), (0,1) etc.
Des avantages de l'un ou de l'autre? Pensées?
Merci à tous! Il y a beaucoup d'informations intéressantes et d'autres références dans ces réponses!
python
python-3.x
generator
yield
cschol
la source
la source
Réponses:
Il n'y a que de légères différences entre les deux. Vous pouvez utiliser le
dis
module pour examiner ce genre de choses par vous-même.Edit: Ma première version a décompilé l'expression du générateur créée au module-scope dans l'invite interactive. C'est légèrement différent de la version de l'OP car il est utilisé dans une fonction. J'ai modifié cela pour correspondre au cas réel de la question.
Comme vous pouvez le voir ci-dessous, le générateur "yield" (premier cas) a trois instructions supplémentaires dans la configuration, mais du premier,
FOR_ITER
elles ne diffèrent que sur un seul point: l'approche "yield" utilise unLOAD_FAST
à la place d'unLOAD_DEREF
à l'intérieur de la boucle. LeLOAD_DEREF
est "plutôt plus lent" queLOAD_FAST
, donc cela rend la version "yield" légèrement plus rapide que l'expression du générateur pour des valeurs suffisamment grandes dex
(la boucle externe) car la valeur dey
est chargée légèrement plus rapidement à chaque passage. Pour des valeurs plus petites,x
ce serait légèrement plus lent en raison de la surcharge supplémentaire du code de configuration.Il pourrait également être intéressant de souligner que l'expression du générateur serait généralement utilisée en ligne dans le code, plutôt que de l'encapsuler avec la fonction comme ça. Cela supprimerait un peu la surcharge de configuration et garderait l'expression du générateur légèrement plus rapide pour des valeurs de boucle plus petites, même si cela
LOAD_FAST
donnait un avantage à la version "yield" autrement.Dans un cas comme dans l'autre, la différence de performance ne suffirait pas à justifier le choix entre l'un ou l'autre. La lisibilité compte beaucoup plus, utilisez donc celui qui vous semble le plus lisible pour la situation actuelle.
la source
LOAD_DEREF
est "plutôt lente", donc si les performances importaient vraiment, un timing réeltimeit
serait bon. Une analyse théorique ne va pas plus loin.Dans cet exemple, pas vraiment. Mais
yield
peut être utilisé pour des constructions plus complexes - par exemple, il peut également accepter des valeurs de l'appelant et modifier le flux en conséquence. Lisez PEP 342 pour plus de détails (c'est une technique intéressante à connaître).Quoi qu'il en soit, le meilleur conseil est d' utiliser ce qui est le plus clair pour vos besoins .
PS Voici un exemple de coroutine simple de Dave Beazley :
la source
Il n'y a aucune différence pour le type de boucles simples que vous pouvez insérer dans une expression de générateur. Cependant, le rendement peut être utilisé pour créer des générateurs qui effectuent un traitement beaucoup plus complexe. Voici un exemple simple pour générer la séquence de fibonacci:
la source
Dans l'utilisation, notez une distinction entre un objet générateur et une fonction générateur.
Un objet générateur est à usage unique, contrairement à une fonction générateur, qui peut être réutilisée chaque fois que vous l'appelez à nouveau, car elle renvoie un nouvel objet générateur.
Les expressions génératrices sont en pratique généralement utilisées «brutes», sans les envelopper dans une fonction, et elles renvoient un objet générateur.
Par exemple:
qui sort:
Comparez avec une utilisation légèrement différente:
qui sort:
Et comparez avec une expression de générateur:
qui produit également:
la source
L'utilisation
yield
est bien si l'expression est plus compliquée que de simples boucles imbriquées. Entre autres, vous pouvez renvoyer une première ou une dernière valeur spéciale. Considérer:la source
En pensant aux itérateurs, le
itertools
module:Pour la performance, pensez
itertools.product(*iterables[, repeat])
la source
Oui, il y a une différence.
Pour l'expression du générateur
(x for var in expr)
,iter(expr)
est appelée lorsque l'expression est créée .Lors de l'utilisation
def
etyield
pour créer un générateur, comme dans:iter(expr)
n'est pas encore appelé. Il ne sera appelé que lors de l'itérationg
(et peut ne pas être appelé du tout).Prenant cet itérateur comme exemple:
Ce code:
tandis que:
Comme la plupart des itérateurs ne font pas beaucoup de choses
__iter__
, il est facile de rater ce comportement. Un exemple concret serait celui de DjangoQuerySet
, qui récupère les données__iter__
etdata = (f(x) for x in qs)
peut prendre beaucoup de temps, tandis quedef g(): for x in qs: yield f(x)
suivi pardata=g()
reviendrait immédiatement.Pour plus d'informations et la définition formelle, reportez-vous à PEP 289 - Expressions de générateur .
la source
Il y a une différence qui pourrait être importante dans certains contextes qui n'a pas encore été signalée. L'utilisation
yield
vous empêche d'utiliserreturn
pour autre chose que de déclencher implicitement StopIteration (et les choses liées aux coroutines) .Cela signifie que ce code est mal formé (et le transmettre à un interprète vous donnera un
AttributeError
):D'un autre côté, ce code fonctionne comme un charme:
la source