Utiliser pour & else dans les générateurs Python
Question
Je suis un grand fan de la de ... autre syntaxe de Python - il est étonnant de constater à quelle fréquence il est applicable et à quel point il peut simplifier le code.
Cependant, je n’ai pas trouvé de bonne façon de l’utiliser dans un générateur, par exemple:
def iterate(i):
for value in i:
yield value
else:
print 'i is empty'
Dans l'exemple ci-dessus, j'aimerais que l'instruction print
ne soit exécutée que si i
est vide. Cependant, comme else
ne respecte que break
et return
, il est toujours exécuté, quelle que soit la longueur de i
.
S'il est impossible d'utiliser pour ... else
de cette manière, quelle est la meilleure approche à suivre pour que l'instruction print
ne soit exécutée que si rien n'est généré?
La solution
Vous cassez la définition d'un générateur, qui devrait générer une exception StopIteration à la fin de l'itération (qui est automatiquement gérée par une instruction return dans une fonction de générateur)
Donc:
def iterate(i):
for value in i:
yield value
return
Il est préférable de laisser le code appelant gérer le cas d'un itérateur vide:
count = 0
for value in iterate(range([])):
print value
count += 1
else:
if count == 0:
print "list was empty"
Peut-être une façon plus propre de faire ce qui précède, mais cela devrait fonctionner correctement et ne tombe pas dans les pièges habituels consistant à "traiter un itérateur comme une liste", ci-dessous.
Autres conseils
Il y a plusieurs façons de le faire. Vous pouvez toujours utiliser directement Iterator
:
def iterate(i):
try:
i_iter = iter(i)
next = i_iter.next()
except StopIteration:
print 'i is empty'
return
while True:
yield next
next = i_iter.next()
Mais si vous savez plus à quoi vous attendre de l'argument i
, vous pouvez être plus concis:
def iterate(i):
if i: # or if len(i) == 0
for next in i:
yield next
else:
print 'i is empty'
raise StopIteration()
Résumant certaines des réponses précédentes, le problème pourrait être résolu comme suit:
def iterate(i):
empty = True
for value in i:
yield value
empty = False
if empty:
print "empty"
donc il n'y a vraiment pas de "autre" clause concernée.
Comme vous le constatez, for..else
détecte uniquement une rupture
. Cela s'applique donc uniquement lorsque vous recherchez quelque chose, puis arrêtez .
Cela ne s'applique pas à votre objectif, non pas parce que c'est un générateur, mais parce que vous voulez traiter tous les éléments, sans vous arrêter (parce que vous voulez tous les céder, mais ce n'est pas le but).
Donc générateur ou pas, vous avez vraiment besoin d’un booléen, comme dans la solution de Ber.
S'il est impossible d'utiliser pour ... autrement de cette manière, quelle est la meilleure approche pour que l'instruction print ne soit exécutée que si rien n'est généré?
Je peux penser au maximum:
>>> empty = True
>>> for i in [1,2]:
... empty = False
... if empty:
... print 'empty'
...
>>>
>>>
>>> empty = True
>>> for i in []:
... empty = False
... if empty:
... print 'empty'
...
empty
>>>
Qu'en est-il simple si-sinon?
def iterate(i):
if len(i) == 0: print 'i is empty'
else:
for value in i:
yield value