manière idiomatiques de prendre des mesures à tenter de boucler sur un itérables vide
-
28-09-2019 - |
Question
Supposons que je bouclez un itérables et je voudrais prendre des mesures si le iterator est vide. Les deux meilleures façons que je peux penser à le faire sont les suivants:
for i in iterable:
# do_something
if not iterable:
# do_something_else
et
empty = True
for i in iterable:
empty = False
# do_something
if empty:
# do_something_else
Le premier dépend de la iterable étant l'une collection (donc inutile lorsque le iterable est transmis à la fonction / procédé dans lequel la boucle est) et les seconds ensembles empty
à chaque passage à travers la boucle qui semble laid.
Y at-il une autre façon que je manque ou est la deuxième solution le meilleur? Il serait vraiment cool s'il y avait une clause que je pourrais ajouter à la déclaration de la boucle qui traiterait pour moi un peu comme else
fait des drapeaux not_found
disparaissent.
Je ne cherche hacks intelligent.
Je ne cherche pas des solutions qui impliquent beaucoup de code
Je cherche une simple fonction de la langue. Je cherche un clair et pythonique moyen d'itérer sur une itérables et prendre des mesures si le itérables est vide que tout programmeur expérimenté python sera comprendre. Si je pouvais le faire sans mettre un drapeau à chaque itération, ce serait fantastique. S'il n'y a pas de langage simple qui fait cela, alors oublier.
La solution
Je pense que ce le plus propre façon de le faire:
# first try with exceptions
def nonempty( iter ):
""" returns `iter` if iter is not empty, else raises TypeError """
try:
first = next(iter)
except StopIteration:
raise TypeError("Emtpy Iterator")
yield first
for item in iter:
yield item
# a version without exceptions. Seems nicer:
def isempty( iter ):
""" returns `(True, ())` if `iter` if is empty else `(False, iter)`
Don't use the original iterator! """
try:
first = next(iter)
except StopIteration:
return True, ()
else:
def iterator():
yield first
for item in iter:
yield item
return False, iterator()
for x in ([],[1]):
# first version
try:
list(nonempty(iter(x))) # trying to consume a empty iterator raises
except TypeError:
print x, "is empty"
else:
print x, "is not empty"
# with isempty
empty, it = isempty(iter(x))
print x, "is", ("empty" if empty else "not empty")
Autres conseils
Ceci est tout à fait hackish, mais vous pouvez supprimer i
et vérifiez si elle existe après la boucle (sinon, la boucle n'a jamais eu lieu):
try:
del i
except NameException: pass
for i in iterable:
do_something(i)
try:
del i
except NameException:
do_something_else()
Je pense que c'est probablement plus laid que d'utiliser simplement un drapeau si
Mise à jour 2
J'ai aimé Odomontois de réponse. À mon humble avis, il est mieux adapté à ce problème que ce que je l'ai écrit ci-dessous.
Mise à jour
(Après avoir lu le commentaire et la question sous la direction de l'OP) Vous pouvez le faire aussi. Voir ci-dessous:
def with_divisible(n, a, b, f):
it = (i for i in xrange(a, b) if not i % n)
for i in wrapper(it):
f(i)
>>> with_divisible(1, 1, 1, lambda x: x)
Traceback (most recent call last):
File "<pyshell#55>", line 1, in <module>
with_divisible(1, 1, 1, lambda x: x)
File "<pyshell#54>", line 3, in with_divisible
for i in wrapper(it):
File "<pyshell#46>", line 4, in wrapper
raise EmptyIterableException("Empty")
EmptyIterableException: Empty
>>> with_divisible(7, 1, 21, lambda x: x)
7
14
...Snipped...
raise EmptyIterableException("Empty")
EmptyIterableException: Empty
Réponse originale
Problème intéressant. Je l'ai fait quelques expériences et est venu avec ce qui suit:
class EmptyIterableException(Exception):
pass
def wrapper(iterable):
for each in iterable:
yield each
raise EmptyIterableException("Empty")
try:
for each in wrapper(iterable):
do_something(each)
except EmptyIterableException, e:
do_something_else()
if not map(do_something_callable,iterable) :
# do something else
La façon générale avant si un itérateur doit être vérifié avant d'être partiellement consommée est d'utiliser itertools.tee
. De cette façon, nous pouvons avoir deux copies du iterator et vérifier un pour le vide tout en consommant l'autre copie depuis le début.
from itertools import tee
it1, it2 = tee(iterable)
try:
it1.next()
for i in it2:
do_some_action(i) #iterator is not empty
except StopIteration:
do_empty_action() #iterator is empty
L'exception StopIteration
est lié à la suite de l'appel à it1.next()
, comme toutes les exceptions soulevées StopIteration
froom intérieur de la boucle se terminera cette boucle.
Modifier : pour ceux qui ne le font pas comme ces exceptions, islice
peut être utilisé pour mettre en place une seule boucle étape:
from itertools import tee, islice
it1, it2 = tee(iterable)
for _ in islice(it1, 1):
#loop entered if iterator is not empty
for i in it2:
do_some_action(i)
break #if loop entered don't execute the else section
else:
do_empty_action()
Personnellement, je préfère le premier style. YMMV.
Qu'en est-il marche arrière "si" et "pour":
if iterable:
for i in iterable:
do_something(i)
else:
do_something_else()
OK, cela ne fonctionne pas!
Voici une autre solution: http: // code.activestate.com/recipes/413614-testing-for-an-empty-iterator/
Ceci est une combinaison de Michael Mrozek 's et FM de réponses:
def with_divisible(n, a, b, f):
'''apply f to every integer x such that n divides x and a <= x < b'''
it = (i for i in xrange(a, b) if not i % n)
for i in it:
f(i)
try: i # test if `it` was empty
except NameError: print('do something else')
def g(i):
print i,
with_divisible( 3, 1, 10, g) # Prints 3 6 9.
with_divisible(33, 1, 10, g) # Prints "do something else"
Les générateurs ont une propriété « gi_frame » qui est une fois Aucun générateur est épuisé, mais seulement après StopIteration a été soulevée. Si c'est acceptable, de quelque chose d'ici, vous pouvez essayer:
import types
def do(x, f, f_empty):
if type(x) == types.GeneratorType:
# generators have a 'gi_frame' property,
# which is None once the generator is exhausted
if x.gi_frame:
# not empty
return f(x)
return f_empty(x)
if x:
return f(x)
return f_empty(x)
def nempty(lst):
print lst, 'not empty'
def empty(lst):
print 'Twas empty!'
# lists
do([2,3,4], nempty, empty)
do([], nempty, empty)
# generators
do((i for i in range(5)), nempty, empty)
gen = (i for i in range(1))
gen.next()
try:
gen.next()
except StopIteration:
pass
do(gen, nempty, empty)