2010-06-24 8 views
61

Actualmente estoy haciendo esto:One-liner para comprobar si un iterador produce al menos un elemento?

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

pero me gustaría una expresión que pueda colocar dentro de un simple if comunicado. ¿Hay algo incorporado que haría que este código parezca menos torpe?

any() devuelve False si un iterable está vacío, pero posiblemente iterará sobre todos los elementos si no lo está. Solo lo necesito para verificar el primer artículo.


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

+1

también un problema con ese código es que no puedes empaquetarlo en una función, porque comerá el primer elemento. Buena pregunta. – andrewrk

+1

En mi caso, no necesito el elemento en absoluto, solo quiero saber que hay al menos un elemento. –

+2

¡ja! ¡Mi mismo caso de uso al tratar de encontrar la misma solución! – Daniel

Respuesta

102

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

+0

Esto parece funcionar para mí, con un re.finditer. Puede probar que cualquier parada en el primer éxito sea fácil: ejecute 'any ((x> 100 para x en xrange (10000000)))' y luego ejecute 'any ((x> 10000000 para x en xrange (100000000)))' - - el segundo debería tomar mucho más tiempo. – chbrown

+0

Esto funciona para el caso de "al menos x" 'suma (1 para _ en itertools.islice (iterator, max_len))> = max_len' –

+8

Del mismo modo, si necesita comprobar si el iterador está vacío, se podría usar' all (Falso para _ en el iterador) 'verificará si el iterador está vacío. (todo regresa verdadero si el iterador está vacío; de lo contrario, se detiene cuando ve el primer elemento falso) – KGardevoir

0

__length_hint__estima la longitud de list(it) - es método privado, sin embargo:

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

no garantizado para cada iterador. >>> def que(): ... dió 1 ... producir 2 ... dar 3 ... >>> i = it() >>> i .__ length_hint__ Rastreo (última llamada más reciente): Archivo "", línea 1, en AttributeError: el objeto 'generador' no tiene atributo '__length_hint__' – andrewrk

+3

Probablemente también sea legal devolver 0 para un iterador que tenga más de cero entradas , ya que es solo una pista. –

18

esto no es realmente más limpio, pero muestra una manera de empaquetar 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 el next predeterminado.

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

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

+0

Esta es probablemente la mejor manera de hacerlo. Sin embargo, ayudaría saber qué está tratando de hacer el OP. Probablemente haya una solución más elegante (esto es IS Python, después de todo). – rossipedia

+0

Gracias, creo que voy a usar 'next()'. –

+1

@Bastien, bien, pero hazlo con un _sentinel_ apropiado (mira mi respuesta). –

5

puede utilizar:

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

pero es un nonexplanatory bits para el lector de código de

+2

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

0

Ésta es una envoltura iterador exceso que por lo general permite comprobar si hay un siguiente punto (a través de la conversión a Boolean) . 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] 
31

En Python 2.6+, si el nombre sentinel está ligado a un valor que el repetidor no puede ceder,

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

Si usted no tiene idea de lo que el iterador posiblemente ceda, haga su propio centinela (por ejemplo, en la parte superior de su módulo) con

sentinel = object() 

De lo contrario, podría usar, en el rol de centinela, cualquier valor que "conozca" (basado en consideraciones de la aplicación) que el iterador no puede ceder.

0

Un poco tarde, pero ... Se 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. 
+2

Esto es ineficiente porque enumera la lista completa. No funcionará para generadores infinitos. – becko

+0

@becko: De acuerdo. Pero ese no parece ser el caso en la pregunta original. – Jens

+1

Otro problema es que el iterador podría generar una cantidad infinita de objetos que pueden provocar el desbordamiento de la memoria, y el hecho de que el programa nunca llegará a la siguiente declaración –

1

¿Qué hay de:

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 
+0

¡Interesante! Pero, ¿y si next() devuelve False porque es lo que realmente se produjo? –

Cuestiones relacionadas