Idiomatische Weg Wirkung Nahme Versuch Schleife über einen leeren iterable
-
28-09-2019 - |
Frage
Nehmen wir an, dass ich über einen iterable bin Looping und möchte einige Maßnahmen ergreifen, wenn der Iterator ist leer. Die beiden besten Möglichkeiten, dass ich mir vorstellen kann, dies zu tun, sind:
for i in iterable:
# do_something
if not iterable:
# do_something_else
und
empty = True
for i in iterable:
empty = False
# do_something
if empty:
# do_something_else
Das hängt zunächst von der die iterable einer Sammlung ist (so nutzlos, wenn die iterable in die Funktion / Methode übergeben werden, wo die Schleife ist) und die zweiten Sätze empty
auf jedem Durchlauf durch die Schleife, die hässlich sein scheint.
Gibt es eine andere Art und Weise, dass ich fehle oder ist die zweite Alternative der Beste? Es wäre cool, wenn es wirklich einig Klausel ist, dass ich zu der Schleife Aussage könnte hinzufügen, dass dies für mich behandeln würde viel wie else
not_found
Fahnen weg gehen macht.
Ich bin nicht für klug Hacks suchen.
Ich bin nicht auf der Suche nach Lösungen, die eine Menge Code beinhalten
Ich bin auf der Suche nach einer einfachen Sprache-Funktion. Ich bin auf der Suche nach einem klar und pythonic Art und Weise zu iterieren über eine iterable und einige Maßnahmen ergreifen, wenn die iterable leer ist, dass jeder erfahrene Python-Programmierer werden verstehen. Wenn ich es ohne Setzen eines Flags bei jeder Iteration tun könnte, wäre das fantastisch. Wenn es keine einfache Sprache ist, das dies tut, dann vergessen Sie es.
Lösung
Ich denke, das das der sauberste Weg, dies zu tun:
# 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")
Andere Tipps
Das ist ganz hackish, aber Sie können i
löschen und dann prüfen, ob es nach der Schleife vorhanden ist (wenn nicht, wird die Schleife noch nie passiert):
try:
del i
except NameException: pass
for i in iterable:
do_something(i)
try:
del i
except NameException:
do_something_else()
Ich denke, das ist wahrscheinlich hässlicher als nur eine Fahne mit obwohl
Update 2
Ich mochte Odomontois' Antwort . IMHO ist es besser, dieses Problem geeignet als das, was ich unten geschrieben.
Aktualisieren
(nach dem OP Kommentar zu lesen und bearbeitet Frage) Sie können das auch tun. Siehe unten:
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
Original Antwort
Interessantes Problem. Ich habe einige Experimente und kam mit dem Follow-up:
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
Der allgemeine Weg nach vorne, wenn ein Iterator teilverbrauchten geprüft werden soll, bevor sie ist itertools.tee
zu verwenden. Auf diese Weise können wir zwei Kopien des Iterator haben und prüfen eine für Leere, während immer noch die andere Kopie von Anfang an raubend.
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
Die StopIteration
Ausnahme gebunden ist, ein Ergebnis des Aufrufs zu it1.next()
sein, wie alle StopIteration
Ausnahmen angehoben froom innerhalb der Schleife wird diese Schleife beenden.
Bearbeiten : für diejenigen, die nicht wie solche Ausnahmen, islice
verwendet werden kann, einen einzigen Schritt Schleife einzurichten:
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()
Ich persönlich bevorzuge den ersten Stil. YMMV.
Was ist Umkehr "wenn" und "für":
if iterable:
for i in iterable:
do_something(i)
else:
do_something_else()
OK, das funktioniert nicht!
Hier ist eine andere Lösung: http: // code.activestate.com/recipes/413614-testing-for-an-empty-iterator/
Dies ist eine Kombination von Michael Mrozek 's und FM 's Antworten:
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"
Generatoren haben eine ‚gi_frame‘ Eigenschaft, die keine ist, wenn der Generator erschöpft ist, aber erst nach StopIteration angehoben worden. Wenn das akzeptabel ist, ist hier etwas, das Sie könnten versuchen:
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)