Domanda

Supponiamo che sto loop nel corso di un iterabile e vorrei prendere qualche azione se l'iteratore è vuoto. I due modi migliori che mi viene in mente di fare questo sono:

for i in iterable:
     # do_something
if not iterable:
    # do_something_else

e

empty = True
for i in iterable:
    empty = False
    # do_something
if empty:
    # do_something_else

La prima dipende dal iterable essere un insieme (così inutile quando iterable viene passato nella funzione / metodo in cui l'anello è) e secondi gruppi empty su ogni passaggio attraverso il ciclo che sembra brutto.

C'è un altro modo che mi manca o è la seconda alternativa il migliore? Sarebbe davvero bello se ci fosse qualche clausola che ho potuto aggiungere alla dichiarazione ciclo che avrebbe gestito questo per me molto simile else rende bandiere not_found andare via.


Non sto cercando di hack intelligenti.

Io non sono alla ricerca di soluzioni che coinvolgono un sacco di codice

Cerco una semplice caratteristica del linguaggio. Sto cercando un e modo chiaro divinatorio per iterare su un iterabile e prendere qualche azione se l'iterabile è vuota che qualsiasi programmatore python esperto sarà capire. Se potessi farlo senza impostare una bandiera su ogni iterazione, che sarebbe fantastico. Se non c'è semplice linguaggio che fa questo, allora non pensarci più.

È stato utile?

Soluzione

Credo che questo il il modo più pulito per fare questo:

# 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")

Altri suggerimenti

Questo è abbastanza hacker, ma è possibile eliminare i e quindi verificare se esiste dopo il ciclo (in caso contrario, il ciclo non è mai avvenuto):

try:
    del i
except NameException: pass

for i in iterable:
    do_something(i)

try:
    del i
except NameException:
    do_something_else()

Credo che sia probabilmente più brutto di un semplice utilizzando un flag se

Aggiorna 2

mi piaceva Odomontois' risposta . IMHO è più adatto a questo problema di quello che ho scritto qui di seguito.

Aggiorna

(Dopo aver letto il commento del PO e domanda a cura) È possibile fare anche questo. Vedi sotto:

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

risposta originale

Interessante problema. Ho fatto alcuni esperimenti e si avvicinò con la seguente:

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

Il modo generale avanti se un iteratore sarà parzialmente controllato prima di essere consumato è usare itertools.tee. In questo modo possiamo avere due copie del iteratore e verificare uno per il vuoto, pur consumando l'altra copia fin dall'inizio.

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'eccezione StopIteration è destinato ad essere un risultato della chiamata a it1.next(), come tutte le eccezioni sollevate StopIteration froom all'interno del ciclo terminerà quel loop.

Modifica : per coloro che non lo fanno come tali eccezioni, islice può essere utilizzato per impostare un unico ciclo passo:

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()

Io personalmente preferisco il primo stile. YMMV.

Che dire invertire "se" e "per":

if iterable:
    for i in iterable:
        do_something(i)
else:
    do_something_else()

OK, questo non funziona!

Ecco un altra soluzione: http: // code.activestate.com/recipes/413614-testing-for-an-empty-iterator/

Questa è una combinazione di Michael Mrozek 's e FM 's risposte:

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"

I generatori hanno un 'gi_frame' immobile che non è una volta che il generatore è esausto, ma solo dopo StopIteration è stata sollevata. Se questo è accettabile, è qui qualcosa che si potrebbe provare:

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)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top