One-liner para verificar se um iterador produz pelo menos um elemento?


101

Atualmente estou fazendo isso:

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

Mas gostaria de uma expressão que pudesse colocar dentro de uma ifdeclaração simples . Existe algo integrado que tornaria este código menos desajeitado?

any()retorna Falsese um iterável estiver vazio, mas irá potencialmente iterar sobre todos os itens se não estiver. Eu só preciso verificar o primeiro item.


Alguém pergunta o que estou tentando fazer. Escrevi uma função que executa uma consulta SQL e produz seus resultados. Às vezes, quando chamo essa função, só quero saber se a consulta retornou algo e tomar uma decisão com base nisso.


2
Além disso, um problema com esse código é que você não pode empacotá-lo em uma função, porque ele comerá o primeiro elemento. Boa pergunta.
andrewrk

2
No meu caso, não preciso do elemento, só quero saber se há pelo menos um elemento.
Bastien Léonard

2
hah! Meu mesmo caso de uso ao tentar encontrar a mesma solução!
Daniel de


Respostas:


134

anynão irá além do primeiro elemento se for True. No caso de o iterador gerar algo falso, você pode escrever any(True for _ in iterator).


Isso parece funcionar para mim, com um re.finditer. Você pode testar se qualquer um para no primeiro sucesso facilmente: corra any((x > 100 for x in xrange(10000000)))e depois corra any((x > 10000000 for x in xrange(100000000)))- o segundo deve demorar muito mais.
chbrown de

1
Isso funciona para o caso de "pelo menos x"sum(1 for _ in itertools.islice(iterator, max_len)) >= max_len
Dave Butler

11
Da mesma forma, se você precisar verificar se o iterador está vazio, pode-se usar o all(False for _ in iterator)irá verificar se o iterador está vazio. (all retorna True se o iterador estiver vazio, caso contrário, ele para quando vê o primeiro elemento False)
KGardevoir

22
O grande problema com essa solução é que você não pode realmente usar o valor retornado do iterador se não estiver vazio, certo?
Ken Williams

42

No Python 2.6+, se o nome sentinelestiver vinculado a um valor que o iterador não pode produzir,

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

Se você não tem ideia do que o iterador pode produzir, faça seu próprio sentinela (por exemplo, no topo do seu módulo) com

sentinel = object()

Caso contrário, você poderia usar, na função de sentinela, qualquer valor que você "saiba" (com base nas considerações do aplicativo) que o iterador não pode produzir.


1
Agradável! Para o meu caso de uso, if not next(iterator, None):era suficiente, pois eu tinha certeza de que Nenhum não faria parte dos itens. Obrigado por me apontar na direção certa!
wasabigeek de

1
@wasabi Lembre-se de que notretornará True para qualquer objeto falso, como listas vazias, False e zero. is not Noneé mais seguro e, na minha opinião, mais claro.
Caagr98

21

Isso não é realmente mais limpo, mas mostra uma maneira de empacotá-lo em uma função sem perdas:

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

Isso não é realmente pythônico e, para casos particulares, provavelmente existem soluções melhores (mas menos gerais), como o próximo padrão.

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

Isso não é geral porque None pode ser um elemento válido em muitos iteráveis.


Esta é provavelmente a melhor maneira de fazer isso. No entanto, ajudaria saber o que o OP está tentando fazer? Provavelmente existe uma solução mais elegante (afinal, isso é Python).
rossipedia

Obrigado, acho que vou usar next().
Bastien Léonard

1
@Bastien, tudo bem, mas faça isso com uma sentinela apropriada (veja minha resposta).
Alex Martelli

3
Há um grande vazamento de memória nesta solução. Os teeitertools terão que manter todos os elementos do iterador original para o caso de any_checkprecisar avançar. Isso é pior do que apenas converter o iterador original em uma lista.
Rafał Dowgird,

1
@ RafałDowgird Isso é pior do que apenas converter o iterador original em uma lista. Na verdade, não - pense em sequências infinitas.
Piotr Dobrogost

6

você pode usar:

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

mas é um pouco não explicativo para o leitor de código


2
.. (você pode usar qualquer iterável de 1 item em vez de [Nenhum])
mykhal

5

A melhor maneira de fazer isso é com um peekablede more_itertools.

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

Apenas tome cuidado se você mantiver as referências para o iterador antigo, pois ele se tornará avançado. Você tem que usar o novo iterador peekable a partir de então. Na verdade, porém, peekable espera ser o único pedaço de código modificando aquele antigo iterador, então você não deve manter referências para o antigo iterador por aí.


3

A respeito:

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

4
Interessante! Mas e se next () retornar False porque é o que realmente foi gerado?
Bastien Léonard

@ BastienLéonard Crie uma turma class NotSet: passe verifique se háif next(i, NotSet) is NotSet: print("Iterator is empty")
Elijas

-1

__length_hint__ estima a duração de list(it)- é um método privado, embora:

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

4
não garantido para cada iterador. >>> def it (): ... yield 1 ... yield 2 ... yield 3 ... >>> i = it () >>> i .__ length_hint__ Traceback (última chamada mais recente): Arquivo " <stdin> ", linha 1, em <module> AttributeError: o objeto 'generator' não tem atributo ' length_hint '
andrewrk

3
Também é provavelmente válido retornar 0 para um iterador que tenha mais de zero entradas, já que é apenas uma dica.
Glenn Maynard

-1

Este é um invólucro de iterador exagerado que geralmente permite verificar se há um próximo item (via conversão para booleano). Claro, muito ineficiente.

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)

Resultado:

False
[]
True
[1, 2, 3]

-2

Um pouco tarde, mas ... Você poderia transformar o iterador em uma lista e depois trabalhar com essa lista:

# 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.

4
Isso é ineficiente porque enumera a lista inteira. Não funcionará para geradores infinitos.
becko

@becko: Concordo. Mas esse não parece ser o caso da pergunta original.
Jens

3
Outro problema é que o iterador pode gerar uma quantidade infinita de objetos que podem resultar em estouro de memória, e o fato de que o programa nunca alcançará a próxima instrução
Willem Van Onsem
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.