2010-08-15 10 views
5

Angenommen, ich wiederhole ein iterables und möchte etwas unternehmen, wenn der Iterator leer ist. Die beiden besten Möglichkeiten, die ich denken kann, dies zu tun sind:Idiomatische Vorgehensweise beim Versuch, eine leere iterierbare Schleife zu durchlaufen

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 

Die erste auf die hängt die iterable eine Sammlung zu sein (so nutzlos für, wenn der iterable in die übergeben wird Funktion/Methode, wo die Schleife ist) und die zweite setzt empty auf jedem Durchlauf durch die Schleife, die hässlich erscheint.

Gibt es einen anderen Weg, dass ich vermisse oder ist die zweite Alternative die beste? Es wäre wirklich cool, wenn es eine Klausel gäbe, die ich der Schleifenanweisung hinzufügen könnte, die das für mich behandelt, ähnlich wie else Marken not_found Flags weggehen.


Ich bin nicht auf der Suche nach cleveren Hacks.

ich suche nicht nach Lösungen, die eine Menge Code beinhalten

ich für eine einfache Sprache-Funktion suchen. Ich suche nach einer klaren und pythonic Möglichkeit, über eine iterable zu iterieren und etwas zu tun, wenn die iterable leer ist, dass jeder erfahrene Python-Programmierer verstehen wird. Wenn ich es tun könnte, ohne bei jeder Iteration eine Flagge zu setzen, wäre das fantastisch. Wenn es kein einfaches Idiom gibt, das das tut, dann vergiss es.

+0

Das ist natürlich nicht wirklich wichtig, aber ich glaube, der Kommentar in Ihrem Beispiel sollte lauten: a <= x Bolo

+0

@Bolo gut aussehend. – aaronasterling

+0

Siehe auch: http://stackoverflow.com/questions/661603/how-do-i-know-if-a-generator-ist-empty-from-the-start und http://stackoverflow.com/questions/ 1966591/hasnext-in-python-iterators –

Antwort

3

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") 
+0

+1. Das ist die beste Antwort bisher. Ich werde wahrscheinlich diesen akzeptieren. – aaronasterling

3

Dieses ganz hackish ist, aber Sie können i löschen und dann prüfen, ob es nach der Schleife vorhanden ist (falls nicht, die Schleife nie passiert):

try: 
    del i 
except NameException: pass 

for i in iterable: 
    do_something(i) 

try: 
    del i 
except NameException: 
    do_something_else() 

denke ich, das ist wahrscheinlich hässlicher als nur eine Flagge mit obwohl

+1

Das ist schlau, aber wie du es bei deiner letzten Aussage vorausgesehen hast, ist es nicht ganz das, wonach ich suche. Trotzdem verdient Cleverness immer eine +1 – aaronasterling

2

aktualisieren 2

mochte ich Odomontois' answer. IMHO ist es besser geeignet für dieses Problem als das, was ich unten geschrieben habe.

aktualisieren

(nach dem Kommentar des OP 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 folgenden:

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

Entschuldigung, ich habe meine Frage schlecht formuliert. Ich werde meine Frage bearbeiten. – aaronasterling

+0

und ich vermasselte auch meine Bearbeitung. Obwohl, das sollte dich nicht abgeworfen haben. Die Zeile in der for-Schleife liest "empty = False". Ihr Code löst die Ausnahme unabhängig aus. Es funktioniert aber, wenn ich das 'leere' Flag in den Wrapper verschiebe und die Exception auf' if empty' im Wrapper hebe. Tut mir leid, ich bin so wählerisch, aber ich würde die Lösung kaum idiomatisch nennen. Ich habe es jedoch verbessert. – aaronasterling

+0

@aaronasterling: Ihr Ausdruck gibt ein Generatorobjekt zurück. Wird es hilfreich sein, 'next()' auf dem Generator aufzurufen und die 'StopIteration' aufzufangen? –

0

Was „wenn“ und „für“ über Umkehrung:

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/

+3

versuche 'iterable = iter ([])'. – kennytm

+2

Was KennyTM sagte. Ein direkter boolescher Test wird bei Generatoren fehlschlagen. –

+0

Ah ... danke, ich habe mich gefragt, warum alle so komplizierten Code geschrieben haben. – Eike

2
if not map(do_something_callable,iterable) : 
    # do something else 
+0

Wie fängt das überhaupt an, die Frage zu beantworten? – aaronasterling

+0

Eigentlich könnte das ein Versuch wert sein. Der Kartenaufruf "map (f, teilbar (n, a, b))" bildet die Funktion "f" über die Sequenz "teilbar" ab. Wenn "divisibilities" zu Beginn leer war (ich glaube, das war es, was Sie testen wollten), wird das Ergebnis von 'map' eine leere Liste sein und daher wird Zeile # 2 eine Ausnahme auslösen. Wenn jedoch "teilbar" nicht leer ist, erzeugt "map" die Werte von "f" über "teilbar". –

+0

Nein, das ist keinen Versuch wert. Es funktioniert großartig für das spezielle Beispiel, das ich gepostet habe. Es ist nutzlos im weiteren Fall, nach dem ich frage. Bitte lösche diese Antwort. – aaronasterling

0

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" 
0

Generatoren haben eine 'gi _frame'-Eigenschaft, die None ist, sobald der Generator erschöpft ist, aber erst nachdem StopIteration ausgelöst wurde. 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) 
1

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 Iterators haben und einen auf Leerheit prüfen, während wir die andere Kopie von Anfang an konsumieren.

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(), da jede StopIteration Ausnahmen sein innerhalb der Schleife erhöht froom wird diese Schleife beenden.

bearbeiten: für diejenigen, die nur einen einzigen Schritt Schleife einzurichten keine solchen Ausnahmen mögen, können islice verwendet werden:

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 den ersten Stil bevorzugen. YMMV.

Verwandte Themen