Pergunta

Existe uma maneira simples de testar se o gerador não tem itens, como espiada, hasNext, isEmpty, algo nesse sentido?

Foi útil?

Solução

A resposta simples para a pergunta: não, não há nenhuma maneira simples. Há toda uma série de soluções alternativas.

Há realmente não deve ser uma maneira simples, por causa do que geradores são: uma forma de saída de uma sequência de valores sem segurar a seqüência na memória . Portanto, não há passagem de trás.

Você poderia escrever uma função has_next ou talvez até mesmo um tapa-lo para um gerador como um método com um decorador de fantasia, se você quisesse.

Outras dicas

Sugestão:

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.

Uma maneira simples é usar o parâmetro opcional para next () que é utilizado, se o gerador estiver exausta (ou esvaziar). Por exemplo:

iterable = some_generator()

_exhausted = object()

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

Editar:. Corrigido o problema apontado no comentário de mehtunguh

A melhor abordagem, IMHO, seria para evitar um teste especial. Na maioria das vezes, o uso de um gerador de é o teste:

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)

Se isso não é bom o suficiente, você ainda pode realizar um teste explícito. Neste ponto, thing conterá o último valor gerado. Se nada foi gerado, ele será indefinido - a menos que você já tenha definido a variável. Você pode verificar o valor de thing, mas isso é um pouco confiáveis. Em vez disso, basta definir uma bandeira dentro do bloco e verificá-lo depois:

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

next(generator, None) is not None

Ou substitua None mas o valor que você sabe que é não em seu gerador.

Editar : Sim, isso vai saltar 1 item no gerador. Muitas vezes, porém, eu verificar se um gerador está vazio apenas para fins de validação, então realmente não usá-lo. Ou caso contrário eu fazer algo como:

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

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

Isso é, isso funciona se o seu gerador vem de um Função , como em generator().

Eu odeio para oferecer uma segunda solução, especialmente um que eu não usaria mim mesmo, mas, se você absolutamente estiveste para fazer isso e não consumir o gerador, como em outras respostas:

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)

Agora eu realmente não gosto dessa solução, porque acredito que isto não é como geradores estão a ser utilizado.

Desculpem a abordagem óbvia, mas a melhor maneira seria fazer:

for item in my_generator:
     print item

Agora você detectou que o gerador está vazio, enquanto você estiver usando-o. Claro, o item não será exibido se o gerador está vazio.

Este pode não se encaixar exatamente com seu código, mas isso é o que o idioma do gerador é a seguinte:. Iteração, então talvez você pode mudar a sua abordagem um pouco, ou não usar geradores de todo

Eu percebo que este post é de 5 anos, neste ponto, mas eu achei que, enquanto procura uma maneira idiomática de fazer isso, e não viu a minha solução postada. Assim, para a posteridade:

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)

Claro que, como eu tenho certeza que muitos comentaristas irá apontar, este é hacky e só funciona em tudo em certas situações limitadas (onde os geradores são livres de efeitos colaterais, por exemplo). YMMV.

Tudo que você precisa fazer para ver se um gerador está vazia é tentar obter o próximo resultado. Claro, se você não estiver pronto usar esse resultado, então você tem de armazená-lo para devolvê-lo novamente mais tarde.

Aqui está uma classe de invólucro que pode ser adicionado a uma iteração existente para adicionar um teste __nonzero__, assim você pode ver se o gerador está vazio com um if simples. Provavelmente, pode também ser transformado em um 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)

Aqui está como você usá-lo:

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

Note que você pode verificar se há vazio a qualquer momento, não apenas no início da iteração.

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

No final do StopIteration gerador é elevado, uma vez que na sua extremidade caso é alcançado imediatamente, exceção é gerada. Mas normalmente você não deve verificar se há existência de valor seguinte.

Outra coisa que você pode fazer é:

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

No meu caso eu precisava saber se uma série de geradores foi povoada antes de eu passou para uma função, que se fundiu os itens, ou seja, zip(...). A solução é semelhante, mas o suficiente diferente, a partir da resposta aceita:

Definição:

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"

O meu problema particular, tem a propriedade de que os iterables está vazio ou tem exatamente o mesmo número de entradas.

Apenas caiu sobre esta discussão e percebeu que uma forma muito simples e fácil de ler a resposta estava faltando:

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

Se não estamos supor para consumir qualquer item então precisamos re-injetar o primeiro item no gerador:

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

Exemplo:

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

Se você precisa saber antes você usar o gerador, então não, não há nenhuma maneira simples. Se você pode esperar até que após você usou o gerador, há uma maneira simples:

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

Aqui está a minha abordagem simples que eu uso para manter em retornar um iterador ao verificar se algo foi cedido Eu apenas verificar se a execução do loop:

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

Aqui está um decorador simples que envolve o gerador, então ele retorna None se vazio. Isso pode ser útil se as suas necessidades de código para saber se o gerador irá produzir qualquer coisa antes looping através dela.

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

Um exemplo onde isso é útil é em código templates - ou seja Jinja2

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

Basta enrolar o gerador com itertools.chain , put algo que irá representar o final do iterable como o segundo iterable, em seguida, basta verificar para isso.

Ex:

import itertools

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

Agora tudo o que resta é para verificar se esse valor nós acrescentado ao final do iterable, quando você lê-lo, em seguida, que irá significar o fim

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

usando islice você só precisa check-up para a primeira iteração para descobrir se ele está vazio.

a partir itertools importação islice

def isempty (iterable):
Lista de retorno (islice (iterable, 1)) == []

Que tal usar qualquer ()? Eu usá-lo com geradores e está funcionando bem. Aqui não é cara explicando um pouco sobre este

Use a função espiada em 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

O iterador retornado por essa função será equivalente ao original passado como um argumento.

Alertado por Mark Ransom, aqui está uma classe que você pode usar para embrulhar qualquer iterador de modo que você pode espreitar à frente, os valores de empurrar para trás para o fluxo e verificar se há vazio. É uma idéia simples, com uma implementação simples que eu encontrei muito útil no passado.

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)

Eu resolvi-lo usando a função soma. Ver abaixo para um exemplo I utilizado com glob.iglob (que retorna um gerador).

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

* Este será, provavelmente, não trabalho para geradores enorme, mas deve executar bem para listas menores

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top