¿Una línea para comprobar si un iterador produce al menos un elemento?

101

Actualmente estoy haciendo esto:

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

Pero me gustaría una expresión que pueda colocar dentro de una ifdeclaración simple . ¿Hay algo integrado que haga que este código parezca menos torpe?

any()devuelve Falsesi un iterable está vacío, pero potencialmente iterará sobre todos los elementos si no lo está. Solo lo necesito para comprobar el primer elemento.


Alguien pregunta qué estoy tratando de hacer. He escrito una función que ejecuta una consulta SQL y produce sus resultados. A veces, cuando llamo a esta función, solo quiero saber si la consulta devolvió algo y tomar una decisión basada en eso.

Bastien Léonard
fuente
2
También un problema con ese código es que no puede empaquetarlo en una función, porque se comerá el primer elemento. Buena pregunta.
andrewrk
2
En mi caso, no necesito el elemento en absoluto, solo quiero saber que hay al menos un elemento.
Bastien Léonard
2
ja! ¡Mi mismo caso de uso al tratar de encontrar la misma solución!
Daniel
Relacionado: stackoverflow.com/q/661603/281545
Mr_and_Mrs_D
1
También relacionado: hasNext en los iteradores de Python?
user2314737

Respuestas:

134

anyno irá más allá del primer elemento si es True. En caso de que el iterador produzca algo falso, puede escribir any(True for _ in iterator).

Jochen Ritzel
fuente
Esto parece funcionar para mí, con un re.finditer. Puede probar fácilmente que cualquier parada en el primer éxito: ejecute any((x > 100 for x in xrange(10000000)))y luego ejecute any((x > 10000000 for x in xrange(100000000))); el segundo debería llevar mucho más tiempo.
chbrown
1
Esto funciona para el caso de "al menos x"sum(1 for _ in itertools.islice(iterator, max_len)) >= max_len
Dave Butler
11
De manera similar, si necesita verificar si el iterador está vacío, se podría usar all(False for _ in iterator)verificará si el iterador está vacío. (todo devuelve True si el iterador está vacío; de lo contrario, se detiene cuando ve el primer elemento False)
KGardevoir
22
El gran problema con esta solución es que no puede usar el valor devuelto por el iterador si no está vacío, ¿verdad?
Ken Williams
42

En Python 2.6+, si el nombre sentinelestá vinculado a un valor que el iterador no puede producir,

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

Si no tiene idea de lo que posiblemente podría producir el iterador, cree su propio centinela (por ejemplo, en la parte superior de su módulo) con

sentinel = object()

De lo contrario, podría utilizar, en el rol de centinela, cualquier valor que "conozca" (según las consideraciones de la aplicación) que el iterador no pueda obtener.

Alex Martelli
fuente
1
¡Agradable! Para mi caso de uso, if not next(iterator, None):fue suficiente ya que estaba seguro de que Ninguno no sería parte de los elementos. ¡Gracias por señalarme en la dirección correcta!
wasabigeek
1
@wasabi Recuerde que notdevolverá True para cualquier objeto falso, como listas vacías, False y cero. is not Nonees más seguro y, en mi opinión, más claro.
Caagr98
21

Esto no es realmente más limpio, pero muestra una forma de empaquetarlo en una función sin pérdidas:

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

Esto no es realmente pitónico y, para casos particulares, probablemente haya soluciones mejores (pero menos generales), como la siguiente predeterminada.

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

Esto no es general porque None puede ser un elemento válido en muchos iterables.

Matthew Flaschen
fuente
Esta es probablemente la mejor forma de hacerlo. Sin embargo, ¿ayudaría saber qué está tratando de hacer el OP? Probablemente haya una solución más elegante (después de todo, este IS Python).
rossipedia
Gracias, creo que lo voy a usar next().
Bastien Léonard
1
@Bastien, bien, pero hazlo con un centinela apropiado (mira mi respuesta).
Alex Martelli
3
Hay una gran pérdida de memoria en esta solución. Las teein itertools tendrán que mantener cada elemento del iterador original en caso de que any_checkalguna vez necesite avanzar. Esto es peor que simplemente convertir el iterador original en una lista.
Rafał Dowgird
1
@ RafałDowgird Esto es peor que simplemente convertir el iterador original en una lista. En realidad, no, piense en secuencias infinitas.
Piotr Dobrogost
6

puedes usar:

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

pero es un poco inexplicable para el lector de código

mykhal
fuente
2
.. (puede usar cualquier iterable de 1 elemento en lugar de [Ninguno])
mykhal
5

La mejor forma de hacerlo es con un peekablefrom more_itertools.

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

Solo tenga cuidado si mantuvo referencias al antiguo iterador, ese iterador avanzará. Tienes que usar el nuevo iterador visible a partir de ese momento. Realmente, sin embargo, peekable espera ser el único fragmento de código que modifique ese antiguo iterador, por lo que no debería mantener referencias al antiguo iterador de todos modos.

enigmático fisico
fuente
3

Qué pasa:

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
Neil
fuente
4
¡Interesante! Pero, ¿qué pasa si next () devuelve False porque es lo que realmente se produjo?
Bastien Léonard
@ BastienLéonard Crea una clase class NotSet: pass, luego verificaif next(i, NotSet) is NotSet: print("Iterator is empty")
Elijas
-1

__length_hint__ estima la longitud de list(it)su método privado, aunque:

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)).
miku
fuente
4
no garantizado para todos los iteradores. >>> def it (): ... rendimiento 1 ... rendimiento 2 ... rendimiento 3 ... >>> i = it () >>> i .__ length_hint__ Traceback (última llamada más reciente): Archivo " <stdin> ", línea 1, en <module> AttributeError: el objeto 'generador' no tiene atributo ' length_hint '
andrewrk
3
También es probablemente legal que devuelva 0 para un iterador que tiene más de cero entradas, ya que es solo una pista.
Glenn Maynard
-1

Este es un contenedor de iterador exagerado que generalmente permite verificar si hay un elemento siguiente (a través de la conversión a booleano). Por supuesto bastante 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)

Salida:

False
[]
True
[1, 2, 3]
doble
fuente
-2

Un poco tarde, pero ... podría convertir el iterador en una lista y luego trabajar con esa 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.
Jens
fuente
4
Esto es ineficaz porque enumera toda la lista. No funcionará para generadores infinitos.
becko
@becko: De acuerdo. Pero ese no parece ser el caso en la pregunta original.
Jens
3
Otro problema es que el iterador podría generar una cantidad infinita de objetos que pueden resultar en un desbordamiento de la memoria y el hecho de que el programa nunca llegará a la siguiente declaración
Willem Van Onsem