Pregunta

¿Hay una manera simple de probar si el generador no tiene elementos, como peek, hasNext, isEmpty, algo así?

¿Fue útil?

Solución

La respuesta simple a su pregunta: no, no hay una manera simple. Hay muchas soluciones alternativas.

Realmente no debería haber una forma simple, debido a lo que son los generadores: una forma de generar una secuencia de valores sin mantener la secuencia en la memoria . Por lo tanto, no hay recorrido hacia atrás.

Podría escribir una función has_next o incluso ponerla en un generador como método con un elegante decorador si lo desea.

Otros consejos

Sugerencia:

def peek(iterable):
    try:
        first = next(iterable)
    except StopIteration:
        return None
    return first, itertools.chain([first], iterable)

Uso:

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.

Una manera simple es usar el parámetro opcional para next () que se usa si el generador está agotado (o vacío). Por ejemplo:

iterable = some_generator()

_exhausted = object()

if next(iterable, _exhausted) == _exhausted:
    print('generator is empty')

Editar: se corrigió el problema señalado en el comentario de mehtunguh.

El mejor enfoque, en mi humilde opinión, sería evitar una prueba especial. La mayoría de las veces, el uso de un generador es la prueba:

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)

Si eso no es lo suficientemente bueno, aún puede realizar una prueba explícita. En este punto, thing contendrá el último valor generado. Si no se generó nada, será indefinido, a menos que ya haya definido la variable. Puede verificar el valor de <=>, pero eso es poco confiable. En su lugar, simplemente establezca una bandera dentro del bloque y verifíquela después:

if not thing_generated:
    print "Avast, ye scurvy dog!"

next(generator, None) is not None

O reemplace None pero cualquier valor que sepa es no en su generador.

Editar : Sí, esto saltará 1 elemento en el generador. A menudo, sin embargo, verifico si un generador está vacío solo para fines de validación, luego realmente no lo uso. O de lo contrario hago algo como:

def foo(self):
    if next(self.my_generator(), None) is None:
        raise Exception("Not initiated")

    for x in self.my_generator():
        ...

Es decir, esto funciona si su generador proviene de una función , como en generator().

Odio ofrecer una segunda solución, especialmente una que no usaría yo mismo, pero, si absolutamente tuviera para hacer esto y no consumir el generador, como en otras respuestas:

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)

Ahora realmente no me gusta esta solución, porque creo que no es así como se deben usar los generadores.

Perdón por el enfoque obvio, pero la mejor manera sería hacerlo:

for item in my_generator:
     print item

Ahora ha detectado que el generador está vacío mientras lo está utilizando. Por supuesto, el elemento nunca se mostrará si el generador está vacío.

Esto puede no coincidir exactamente con su código, pero para eso está el modismo del generador: iterar, por lo que quizás podría cambiar su enfoque ligeramente o no usar generadores en absoluto.

Me doy cuenta de que esta publicación tiene 5 años en este momento, pero la encontré mientras buscaba una forma idiomática de hacerlo, y no vi mi solución publicada. Entonces para la posteridad:

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)

Por supuesto, como estoy seguro de que muchos comentaristas señalarán, esto es hacky y solo funciona en ciertas situaciones limitadas (donde los generadores están libres de efectos secundarios, por ejemplo). YMMV.

Todo lo que necesita hacer para ver si un generador está vacío es intentar obtener el siguiente resultado. Por supuesto, si no está listo para usar ese resultado, debe almacenarlo para devolverlo más tarde.

Aquí hay una clase de contenedor que se puede agregar a un iterador existente para agregar una prueba __nonzero__, para que pueda ver si el generador está vacío con un simple if. Probablemente también se pueda convertir en un decorador.

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)

Así es como lo usarías:

with open(filename, 'r') as f:
    f = GenWrapper(f)
    if f:
        print 'Not empty'
    else:
        print 'Empty'

Tenga en cuenta que puede verificar el vacío en cualquier momento, no solo al comienzo de la iteración.

>>> gen = (i for i in [])
>>> next(gen)
Traceback (most recent call last):
  File "<pyshell#43>", line 1, in <module>
    next(gen)
StopIteration

Al final del generador StopIteration se genera, ya que en su caso se alcanza el final de inmediato, se genera una excepción. Pero normalmente no debe verificar la existencia del siguiente valor.

otra cosa que puedes hacer es:

>>> gen = (i for i in [])
>>> if not list(gen):
    print('empty generator')

En mi caso, necesitaba saber si un grupo de generadores estaba lleno antes de pasarlo a una función, que fusionó los elementos, es decir, zip(...). La solución es similar, pero lo suficientemente diferente, de la respuesta aceptada:

Definición:

def has_items(iterable):
    try:
        return True, itertools.chain([next(iterable)], iterable)
    except StopIteration:
        return False, []

Uso:

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"

Mi problema particular tiene la propiedad de que los iterables están vacíos o tienen exactamente el mismo número de entradas.

Simplemente caí en este hilo y me di cuenta de que faltaba una respuesta muy simple y fácil de leer:

def is_empty(generator):
    for item in generator:
        return False
    return True

Si se supone que no debemos consumir ningún artículo, entonces debemos reinyectar el primer artículo en el generador:

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

Ejemplo:

>>> 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]

Si necesita saber antes de usar el generador, entonces no, no hay una manera simple. Si puede esperar hasta que después de haya utilizado el generador, hay una manera simple:

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

Aquí está mi enfoque simple que utilizo para seguir devolviendo un iterador mientras verifico si se produjo algo Solo verifico si el ciclo se ejecuta:

        n = 0
        for key, value in iterator:
            n+=1
            yield key, value
        if n == 0:
            print ("nothing found in iterator)
            break

Aquí hay un decorador simple que envuelve el generador, por lo que devuelve Ninguno si está vacío. Esto puede ser útil si su código necesita saber si el generador producirá algo antes de recorrerlo.

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

Uso:

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')

Un ejemplo en el que esto es útil es en el código de plantillas, es decir, jinja2

{% if content_generator %}
  <section>
    <h4>Section title</h4>
    {% for item in content_generator %}
      {{ item }}
    {% endfor %
  </section>
{% endif %}

Simplemente envuelva el generador con itertools.chain , ponga algo que representará el final del iterable como el segundo iterable, luego simplemente verifique eso.

Ej:

import itertools

g = some_iterable
eog = object()
wrap_g = itertools.chain(g, [eog])

Ahora todo lo que queda es verificar el valor que agregamos al final del iterable, cuando lo leas, eso significará el final

for value in wrap_g:
    if value == eog: # DING DING! We just found the last element of the iterable
        pass # Do something

usando islice solo necesita verificar hasta la primera iteración para descubrir si está vacío.

  

de itertools import islice

     

def isempty (iterable):
  & nbsp; & nbsp; & nbsp; & nbsp; lista de retorno (islice (iterable, 1)) == []

¿Qué pasa con el uso de any ()? Lo uso con generadores y funciona bien. Aquí hay un chico explicando un poco sobre esto

Utilice la función peek en 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

El iterador devuelto por esta función será equivalente al original pasado como argumento.

Impulsado por Mark Ransom, aquí hay una clase que puede usar para ajustar cualquier iterador para que pueda mirar hacia adelante, empujar los valores nuevamente en la secuencia y verificar si está vacío. Es una idea simple con una implementación simple que he encontrado muy útil en el pasado.

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)

Lo resolví usando la función de suma. Vea a continuación un ejemplo que utilicé con glob.iglob (que devuelve un generador).

def isEmpty():
    files = glob.iglob(search)
    if sum(1 for _ in files):
        return True
    return False

* Esto probablemente no funcionará para generadores ENORMES, pero debería funcionar bien para listas más pequeñas

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top