Wie kann ich wissen, wenn ein Generator von Anfang an leer ist?
Frage
Gibt es eine einfache Möglichkeit, die Prüfung, ob der Generator keine Elemente hat, wie Peek, hasNext, isEmpty, etwas in diese Richtung?
Lösung
Die einfache Antwort auf Ihre Frage: Nein, es gibt keinen einfachen Weg. Es gibt eine ganze Reihe von Abhilfen.
Es sollte wirklich kein einfacher Weg sein, denn von dem, was Generatoren sind: eine Möglichkeit zur Ausgabe einer Folge von Werten ohne die Sequenz im Speicher zu halten . Es gibt also keine Rückwärts Traversal.
Sie könnten eine has_next Funktion schreiben, oder vielleicht ist es sogar mit einem schicken Dekorateur mit einem Generator als Methode Klaps auf, wenn Sie es wollten.
Andere Tipps
Vorschlag:
def peek(iterable):
try:
first = next(iterable)
except StopIteration:
return None
return first, itertools.chain([first], iterable)
Verbrauch:
res = peek(mysequence)
if res is None:
# sequence is empty. Do stuff.
else:
first, mysequence = res
# Do something with first, maybe?
# Then iterate over the sequence:
for element in mysequence:
# etc.
Ein einfacher Weg ist der optionale Parameter für neben verwenden () , die verwendet wird, wenn der Generator (oder leer) erschöpft ist. Zum Beispiel:
iterable = some_generator()
_exhausted = object()
if next(iterable, _exhausted) == _exhausted:
print('generator is empty')
Edit: das Problem behebt, wies in mehtunguh Kommentar out
.Der beste Ansatz, IMHO, wäre eine besondere Prüfung zu vermeiden. Die meiste Zeit, die Verwendung eines Generators ist der Test:
thing_generated = False
# Nothing is lost here. if nothing is generated,
# the for block is not executed. Often, that's the only check
# you need to do. This can be done in the course of doing
# the work you wanted to do anyway on the generated output.
for thing in my_generator():
thing_generated = True
do_work(thing)
Wenn das nicht gut genug ist, können Sie immer noch einen expliziten Test durchführen. An diesem Punkt thing
wird den letzten Wert erzeugt enthalten. Wenn nichts generiert wurde, wird es nicht definiert - es sei denn, Sie bereits die Variable definiert. Sie können den Wert von thing
überprüfen, aber das ist ein bisschen unzuverlässig. Setzen Sie stattdessen nur ein Flag innerhalb des Blocks und überprüfen Sie es später:
if not thing_generated:
print "Avast, ye scurvy dog!"
next(generator, None) is not None
oder ersetzen None
aber was auch immer Wert, den Sie wissen, dass es nicht in Ihrem Generator.
Bearbeiten : Ja, das wird überspringt 1 Artikel im Generator. Oft aber ich überprüfen, ob ein Generator nur zu Validierungszwecken leer ist, dann nicht wirklich nutzen. Oder sonst kann ich so etwas wie:
def foo(self):
if next(self.my_generator(), None) is None:
raise Exception("Not initiated")
for x in self.my_generator():
...
Das heißt, das funktioniert, wenn Ihr Generator stammt aus einer Funktion , wie in generator()
.
Ich hasse eine zweite Lösung zu bieten, vor allem eine, die ich nicht selbst verwenden würde, aber, wenn Sie absolut had , dies zu tun und auf den Generator nicht zu verbrauchen, wie in anderen Antworten:
def do_something_with_item(item):
print item
empty_marker = object()
try:
first_item = my_generator.next()
except StopIteration:
print 'The generator was empty'
first_item = empty_marker
if first_item is not empty_marker:
do_something_with_item(first_item)
for item in my_generator:
do_something_with_item(item)
Nun weiß nicht, wie ich diese Lösung wirklich, weil ich glaube, dass dies nicht wie Generatoren verwendet werden.
Sorry für die nahe liegende Lösung, aber der beste Weg, zu tun wäre:
for item in my_generator:
print item
Sie haben nun festgestellt, dass der Generator leer ist, während Sie es verwenden. Natürlich Artikel wird nie, wenn der Generator leer angezeigt werden.
Das kann nicht genau mit Ihrem Code passen, aber das ist, was das Idiom des Generators ist: Iterieren, vielleicht könnten Sie Ihren Ansatz etwas ändern, oder Generatoren nicht verwenden
.Ich stellt fest, dass dieser Beitrag gegen die 5 Jahre alt ist an dieser Stelle, aber ich fand es während der Suche nach einem idiomatischen Weg, dies zu tun, und nicht meine Lösung geschrieben sehen. Also für die Nachwelt:
import itertools
def get_generator():
"""
Returns (bool, generator) where bool is true iff the generator is not empty.
"""
gen = (i for i in [0, 1, 2, 3, 4])
a, b = itertools.tee(gen)
try:
a.next()
except StopIteration:
return (False, b)
return (True, b)
Natürlich, wie ich bin sicher, dass viele Kommentatoren weisen darauf hin werden, ist dies Hacky und funktioniert nur überhaupt in bestimmten begrenzten Situationen (wo die Generatoren Nebeneffekt frei sind, zum Beispiel). YMMV.
Alles, was Sie tun müssen, um zu sehen, ob ein Generator leer ist, zu versuchen, das nächste Ergebnis zu erhalten. Natürlich, wenn Sie nicht bereit sind, , dieses Ergebnis verwenden, dann müssen Sie speichern sie es zurückgeben Sie es später erneut.
Hier ist ein Wrapper-Klasse, die einem vorhandenen Iterator hinzugefügt werden können, einen __nonzero__
Test hinzufügen möchten, können Sie so sehen, wenn der Generator mit einem einfachen if
leer. Es kann wohl auch in einen Dekorateur gedreht werden.
class GenWrapper:
def __init__(self, iter):
self.source = iter
self.stored = False
def __iter__(self):
return self
def __nonzero__(self):
if self.stored:
return True
try:
self.value = next(self.source)
self.stored = True
except StopIteration:
return False
return True
def __next__(self): # use "next" (without underscores) for Python 2.x
if self.stored:
self.stored = False
return self.value
return next(self.source)
Hier ist, wie Sie es verwenden würde:
with open(filename, 'r') as f:
f = GenWrapper(f)
if f:
print 'Not empty'
else:
print 'Empty'
Beachten Sie, dass für Leere jederzeit überprüfen können, nicht nur zu Beginn der Iteration.
>>> gen = (i for i in [])
>>> next(gen)
Traceback (most recent call last):
File "<pyshell#43>", line 1, in <module>
next(gen)
StopIteration
Am Ende des Generator StopIteration
angehoben wird, da in Ihrem Fall Ende sofort erreicht wird, Ausnahme. Aber normalerweise sollten Sie nicht überprüfen die Existenz des nächsten Wert.
eine andere Sache, die Sie tun können, ist:
>>> gen = (i for i in [])
>>> if not list(gen):
print('empty generator')
In meinem Fall musste ich wissen, ob eine Vielzahl von Generatoren bevölkert war, bevor ich es weitergegeben an eine Funktion, die die Elemente zusammengefügt, das heißt, zip(...)
. Die Lösung ist ähnlich, aber unterschiedlich genug, von der akzeptierten Antwort:
Definition:
def has_items(iterable):
try:
return True, itertools.chain([next(iterable)], iterable)
except StopIteration:
return False, []
Verbrauch:
def filter_empty(iterables):
for iterable in iterables:
itr_has_items, iterable = has_items(iterable)
if itr_has_items:
yield iterable
def merge_iterables(iterables):
populated_iterables = filter_empty(iterables)
for items in zip(*populated_iterables):
# Use items for each "slice"
Mein besonderes Problem hat die Eigenschaft, dass die Iterables entweder leer sind oder haben genau die gleiche Anzahl von Einträgen.
fiel einfach auf diesen Thread und erkannte, dass eine sehr einfache und leicht zu lesen Antwort fehlte:
def is_empty(generator):
for item in generator:
return False
return True
Wenn wir nicht ein beliebiges Element zu konsumieren annehmen, dann müssen wir auf das erste Element in den Generator wieder zu injizieren:
def is_empty_no_side_effects(generator):
try:
item = next(generator)
def my_generator():
yield item
yield from generator
return my_generator(), False
except StopIteration:
return (_ for _ in []), True
Beispiel:
>>> g=(i for i in [])
>>> g,empty=is_empty_no_side_effects(g)
>>> empty
True
>>> g=(i for i in range(10))
>>> g,empty=is_empty_no_side_effects(g)
>>> empty
False
>>> list(g)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Wenn Sie wissen müssen, vor Sie den Generator verwenden, dann nicht, gibt es keine einfache Möglichkeit ist. Wenn Sie warten, bis können nach Sie den Generator verwendet haben, gibt es eine einfache Art und Weise:
was_empty = True
for some_item in some_generator:
was_empty = False
do_something_with(some_item)
if was_empty:
handle_already_empty_generator_case()
Hier ist mein einfacher Ansatz, den ich bei der Rückkehr einen Iterator zu halten verwenden, während der Überprüfung, ob etwas nachgegeben wurde Ich prüfe nur, wenn die Schleife ausgeführt wird:
n = 0
for key, value in iterator:
n+=1
yield key, value
if n == 0:
print ("nothing found in iterator)
break
Hier ist ein einfacher Dekorateur, der den Generator Wraps, so kehrt Keine, wenn leer. Dies kann nützlich sein, wenn Ihr Code muss wissen, ob der Generator etwas produzieren wird, bevor Looping durch.
def generator_or_none(func):
"""Wrap a generator function, returning None if it's empty. """
def inner(*args, **kwargs):
# peek at the first item; return None if it doesn't exist
try:
next(func(*args, **kwargs))
except StopIteration:
return None
# return original generator otherwise first item will be missing
return func(*args, **kwargs)
return inner
Verbrauch:
import random
@generator_or_none
def random_length_generator():
for i in range(random.randint(0, 10)):
yield i
gen = random_length_generator()
if gen is None:
print('Generator is empty')
Ein Beispiel, wo dies nützlich ist in Templating-Code - das heißt jinja2
{% if content_generator %}
<section>
<h4>Section title</h4>
{% for item in content_generator %}
{{ item }}
{% endfor %
</section>
{% endif %}
Einfach wickeln Sie den Generator mit itertools.chain , setzen etwas, das das Ende des iterable als zweiter iterable vertreten wird, dann einfach für das überprüfen.
Beispiel:
import itertools
g = some_iterable
eog = object()
wrap_g = itertools.chain(g, [eog])
Jetzt alles, was übrig bleibt, ist für diesen Wert prüfen wir zum Ende des iterable angehängt, wenn Sie es dann gelesen, dass das Ende bedeuten wird
for value in wrap_g:
if value == eog: # DING DING! We just found the last element of the iterable
pass # Do something
mit Islice Sie nur auf die erste Iteration prüfen müssen zu entdecken, wenn es leer ist.
von itertools Import Islice
def isempty (abzählbaren):
Rückkehr Liste (Islice (iterable, 1)) == []
Was ist unter Verwendung eines beliebigen ()? Ich benutze es mit Generatoren und es funktioniert gut. hier gibt es Kerl ein wenig über Erklärung dieses
Mit der peek Funktion in cytoolz.
from cytoolz import peek
from typing import Tuple, Iterable
def is_empty_iterator(g: Iterable) -> Tuple[Iterable, bool]:
try:
_, g = peek(g)
return g, False
except StopIteration:
return g, True
Der Iterator von dieser Funktion zurück wird mit dem Original übergibt als Argument äquivalent sein.
Angeregt durch Mark Ransom, hier ist eine Klasse, die Sie jeden Iterator wickeln können, so dass Sie vor spähen können, Push-Werte wieder auf den Strom und prüfen, ob leer. Es ist eine einfache Idee mit einer einfachen Implementierung, dass ich in der Vergangenheit sehr praktisch gefunden habe.
class Pushable:
def __init__(self, iter):
self.source = iter
self.stored = []
def __iter__(self):
return self
def __bool__(self):
if self.stored:
return True
try:
self.stored.append(next(self.source))
except StopIteration:
return False
return True
def push(self, value):
self.stored.append(value)
def peek(self):
if self.stored:
return self.stored[-1]
value = next(self.source)
self.stored.append(value)
return value
def __next__(self):
if self.stored:
return self.stored.pop()
return next(self.source)
ich es gelöst, indem die Summe-Funktion. Siehe unten für ein Beispiel, das ich mit glob.iglob verwendet (die einen Generator zurückgibt).
def isEmpty():
files = glob.iglob(search)
if sum(1 for _ in files):
return True
return False
* Dies wird wahrscheinlich nicht für große Generatoren funktionieren soll aber durchführen gut für kleinere Listen