Einzeiler, um zu überprüfen, ob ein Iterator mindestens ein Element liefert?

99

Derzeit mache ich das:

try:
    something = iterator.next()
    # ...
except StopIteration:
    # ...

Aber ich möchte einen Ausdruck, den ich in eine einfache ifAussage einfügen kann. Ist etwas eingebaut, das diesen Code weniger ungeschickt aussehen lässt?

any()FalseGibt zurück, wenn eine Iterable leer ist. Wenn dies nicht der Fall ist, werden möglicherweise alle Elemente durchlaufen. Ich brauche es nur, um den ersten Artikel zu überprüfen.


Jemand fragt, was ich versuche zu tun. Ich habe eine Funktion geschrieben, die eine SQL-Abfrage ausführt und deren Ergebnisse liefert. Wenn ich diese Funktion aufrufe, möchte ich manchmal nur wissen, ob die Abfrage etwas zurückgegeben hat, und darauf basierend eine Entscheidung treffen.

Bastien Léonard
quelle
2
Ein Problem mit diesem Code ist auch, dass Sie ihn nicht in eine Funktion packen können, da er das erste Element frisst. Gute Frage.
Andrewrk
2
In meinem Fall brauche ich das Element überhaupt nicht, ich möchte nur wissen, dass es mindestens ein Element gibt.
Bastien Léonard
2
hah! Mein gleicher Anwendungsfall beim Versuch, die gleiche Lösung zu finden!
Daniel
Siehe auch
Mr_and_Mrs_D
1
Ebenfalls verwandt: hasNext in Python-Iteratoren?
user2314737

Antworten:

133

anygeht nicht über das erste Element hinaus, wenn es wahr ist. Falls der Iterator etwas Falsches liefert, können Sie schreiben any(True for _ in iterator).

Jochen Ritzel
quelle
Dies scheint für mich mit einem Re-Finder zu funktionieren. Sie können leicht testen, ob ein Stopp beim ersten Erfolg eintritt: Laufen any((x > 100 for x in xrange(10000000)))und dann rennen any((x > 10000000 for x in xrange(100000000)))- der zweite sollte viel länger dauern.
Chbrown
1
Dies funktioniert für den Fall von "mindestens x"sum(1 for _ in itertools.islice(iterator, max_len)) >= max_len
Dave Butler
11
Wenn Sie überprüfen müssen, ob der Iterator leer ist, können Sie all(False for _ in iterator)auch prüfen, ob der Iterator leer ist. (Alle geben True zurück, wenn der Iterator leer ist, andernfalls stoppt er, wenn er das erste False-Element sieht.)
KGardevoir
22
Das große Problem bei dieser Lösung ist, dass Sie den vom Iterator zurückgegebenen Wert nicht verwenden können, wenn er nicht leer ist, oder?
Ken Williams
42

Wenn in Python 2.6+ der Name sentinelan einen Wert gebunden ist, den der Iterator möglicherweise nicht liefern kann,

if next(iterator, sentinel) is sentinel:
    print('iterator was empty')

Wenn Sie keine Ahnung haben, was der Iterator möglicherweise liefern könnte, erstellen Sie Ihren eigenen Sentinel (z. B. oben in Ihrem Modul) mit

sentinel = object()

Andernfalls könnten Sie in der Sentinel-Rolle jeden Wert verwenden, den Sie "kennen" (basierend auf Anwendungsüberlegungen), den der Iterator möglicherweise nicht liefern kann.

Alex Martelli
quelle
1
Nett! Für meinen Anwendungsfall if not next(iterator, None):war ausreichend, da ich sicher war, dass keiner nicht Teil der Artikel sein würde. Danke, dass du mich in die richtige Richtung gelenkt hast!
Wasabigeek
1
@wasabi Denken Sie daran, dass notTrue für jedes falsche Objekt zurückgegeben wird, z. B. für leere Listen, False und Null. is not Noneist sicherer und meiner Meinung nach klarer.
Caagr98
21

Dies ist nicht wirklich sauberer, aber es zeigt eine Möglichkeit, es verlustfrei in eine Funktion zu packen:

def has_elements(iter):
  from itertools import tee
  iter, any_check = tee(iter)
  try:
    any_check.next()
    return True, iter
  except StopIteration:
    return False, iter

has_el, iter = has_elements(iter)
if has_el:
  # not empty

Dies ist nicht wirklich pythonisch, und für bestimmte Fälle gibt es wahrscheinlich bessere (aber weniger allgemeine) Lösungen, wie die nächste Standardeinstellung.

first = next(iter, None)
if first:
  # Do something

Dies ist nicht allgemein, da None in vielen Iterables ein gültiges Element sein kann.

Matthew Flaschen
quelle
Dies ist wahrscheinlich der beste Weg, dies zu tun. Es wäre jedoch hilfreich zu wissen, was das OP versucht. Es gibt wahrscheinlich eine elegantere Lösung (schließlich ist dies Python).
Wikipedia
Danke, ich denke ich werde verwenden next().
Bastien Léonard
1
@ Bastien, gut, aber mach das mit einem passenden Sentinel (siehe meine Antwort).
Alex Martelli
3
Diese Lösung weist einen großen Speicherverlust auf. Die teein itertools müssen jedes einzelne Element vom ursprünglichen Iterator fernhalten, falls es any_checkjemals weitergehen muss. Dies ist schlimmer als nur das Konvertieren des ursprünglichen Iterators in eine Liste.
Rafał Dowgird
1
@ RafałDowgird Dies ist schlimmer als nur den ursprünglichen Iterator in eine Liste zu konvertieren. Nicht wirklich - denken Sie an unendliche Sequenzen.
Piotr Dobrogost
6

Sie können verwenden:

if zip([None], iterator):
    # ...
else:
    # ...

Für den Codeleser ist dies jedoch nicht erklärend

Mykhal
quelle
2
.. (Sie können jedes iterierbare 1-Element anstelle von [Keine] verwenden)
mykhal
5

Der beste Weg, dies zu tun, ist mit einem peekablevon more_itertools.

from more_itertools import peekable
iterator = peekable(iterator)
if iterator:
    # Iterator is non-empty.
else:
    # Iterator is empty.

Passen Sie nur auf, wenn Sie den alten Iterator beibehalten, wird dieser Iterator erweitert. Von da an müssen Sie den neuen Peekable-Iterator verwenden. Wirklich erwartet Peekable jedoch, dass es das einzige Stück Code ist, das diesen alten Iterator modifiziert, so dass Sie die Refs des alten Iterators, der herumliegt, sowieso nicht behalten sollten.

rätselhafter Physiker
quelle
3

Wie wäre es mit:

In [1]: i=iter([])

In [2]: bool(next(i,False))
Out[2]: False

In [3]: i=iter([1])

In [4]: bool(next(i,False))
Out[4]: True
Neil
quelle
4
Interessant! Aber was ist, wenn next () False zurückgibt, weil es das ist, was wirklich ergeben wurde?
Bastien Léonard
@ BastienLéonard Erstelle eine Klasse class NotSet: passund if next(i, NotSet) is NotSet: print("Iterator is empty")
suche
-1

__length_hint__ schätzt die Länge von list(it)- es ist jedoch eine private Methode:

x = iter( (1, 2, 3) )
help(x.__length_hint__)
      1 Help on built-in function __length_hint__:
      2 
      3 __length_hint__(...)
      4     Private method returning an estimate of len(list(it)).
miku
quelle
4
nicht für jeden Iterator garantiert. >>> def it (): ... Ausbeute 1 ... Ausbeute 2 ... Ausbeute 3 ... >>> i = it () >>> i .__ length_hint__ Traceback (letzter Aufruf zuletzt): Datei " <stdin> ", Zeile 1, in <module> AttributeError: Das Objekt 'generator' hat kein Attribut ' length_hint '
andrewrk
3
Es ist wahrscheinlich auch legal, für einen Iterator mit mehr als null Einträgen 0 zurückzugeben, da dies nur ein Hinweis ist.
Glenn Maynard
-1

Dies ist ein Overkill-Iterator-Wrapper, mit dem im Allgemeinen überprüft werden kann, ob ein nächstes Element vorhanden ist (durch Konvertierung in einen Booleschen Wert). Natürlich ziemlich ineffizient.

class LookaheadIterator ():

    def __init__(self, iterator):
        self.__iterator = iterator
        try:
            self.__next      = next (iterator)
            self.__have_next = True
        except StopIteration:
            self.__have_next = False

    def __iter__(self):
        return self

    def next (self):
        if self.__have_next:
            result = self.__next
            try:
                self.__next      = next (self.__iterator)
                self.__have_next = True
            except StopIteration:
                self.__have_next = False

            return result

        else:
            raise StopIteration

    def __nonzero__(self):
        return self.__have_next

x = LookaheadIterator (iter ([]))
print bool (x)
print list (x)

x = LookaheadIterator (iter ([1, 2, 3]))
print bool (x)
print list (x)

Ausgabe:

False
[]
True
[1, 2, 3]
doublep
quelle
-2

Ein bisschen spät, aber ... Sie könnten den Iterator in eine Liste verwandeln und dann mit dieser Liste arbeiten:

# Create a list of objects but runs out the iterator.
l = [_ for _ in iterator]

# If the list is not empty then the iterator had elements; else it was empty.
if l :
    pass # Use the elements of the list (i.e. from the iterator)
else :
    pass # Iterator was empty, thus list is empty.
Jens
quelle
4
Dies ist ineffizient, da die gesamte Liste aufgelistet wird. Funktioniert nicht für unendliche Generatoren.
Becko
@ Becko: Einverstanden. Dies scheint jedoch in der ursprünglichen Frage nicht der Fall zu sein.
Jens
3
Ein weiteres Problem ist, dass der Iterator eine unendliche Anzahl von Objekten generieren kann, die zu einem Speicherüberlauf führen können, und dass das Programm niemals die nächste Anweisung erreicht
Willem Van Onsem