Frage

Gibt es eine einfache Möglichkeit, die Prüfung, ob der Generator keine Elemente hat, wie Peek, hasNext, isEmpty, etwas in diese Richtung?

War es hilfreich?

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

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top