manière idiomatiques de prendre des mesures à tenter de boucler sur un itérables vide

StackOverflow https://stackoverflow.com/questions/3486257

  •  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.

Était-ce utile?

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)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top