modo gergale di agire su tentativo di ciclo su un iterabile vuoto
-
28-09-2019 - |
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ù.
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)