2010-12-29 8 views
8

¿Hay una mejor manera de escribir lo siguiente:¿mejor sintaxis for-loop para detectar secuencias vacías?

row_counter = 0 
    for item in iterable_sequence: 
     # do stuff with the item 

     counter += 1 

    if not row_counter: 
     # handle the empty-sequence-case 

Por favor, tenga en cuenta que no puedo usar len (iterable_sequence) porque: 1) longitudes no todas las secuencias han conocido; 2) en algunos casos, llamar a len() puede desencadenar la carga de los elementos de la secuencia en la memoria (como sería el caso con los resultados de la consulta sql).

La razón por la que pregunto es que simplemente tengo curiosidad si hay una manera de hacerlo más conciso e idiomático. Lo que estoy buscando es a lo largo de las líneas de:

for item in sequence: 
    #process item 
*else*: 
    #handle the empty sequence case 

(suponiendo que "otra cosa" aquí trabajado sólo en secuencias vacías, que sé que no lo hace)

+0

una mejor manera de escribirlo - con qué propósito? – canavanin

+0

para que sea más conciso e idiomático. (Voy a agregar esto a mi pregunta) –

+0

Un ORM podría responder eso por usted; algo así como los métodos '.fetchone()' o '.first()' de DB-API devuelven 'None' si el conjunto de resultados está vacío (sin filas). – jfs

Respuesta

7
for item in iterable: 
    break 
else: 
    # handle the empty-sequence-case here 

O

item = next(iterator, sentinel) 
if item is sentinel: 
    # handle the empty-sequence-case here 

En cada caso se consume un artículo si está presente.


Un ejemplo de aplicación empty_adapter() 's se ha mencionado en los comentarios:

def empty_adaptor(iterable, sentinel=object()): 
    it = iter(iterable) 
    item = next(it, sentinel) 
    if item is sentinel: 
     return None # empty 
    else: 
     def gen(): 
      yield item 
      for i in it: 
       yield i 
     return gen() 

se podría utilizar de la siguiente manera:

it = empty_adaptor(some_iter) 
if it is not None: 
    for i in it: 
     # handle items 
else: 
    # handle empty case 

Presentación de caso especial de una secuencia vacía de un general el caso parece incorrecto. Debería haber una mejor solución para un problema específico del dominio.

+1

Necesito procesar todos los artículos en la lista, no solo el primero. Para procesar el resto de los elementos, tendré que configurar otro ciclo for, lo que hará que toda la solución sea aún menos concisa que la original. –

+0

Parece que consumir un artículo no es una opción para el O.P. ya que tiene que "hacer cosas" con cada artículo. – jsbueno

+0

@Dmitry Beransky: si necesita consumir todos los elementos, puede escribir un adaptador que acepte un iterable como entrada y devuelva 'None 'si está vacío o produce la misma secuencia de elementos que su entrada. Para la implementación, podría usar la variante más legible entre las proporcionadas en esta pregunta hasta el momento. – jfs

0
if not iterable_sequence.__length_hint__(): 
    empty() 
else: 
    for item in iterable_sequence: 
     dostuff() 
+0

¿Esto no activaría una llamada a len()? –

+0

Desencadenaría una llamada a len() para secuencias que ya están evaluadas, lo cual no es problema - no llamará a 'len' en otros iterables - porque generalmente no tienen un' len' de todos modos. Sin embargo, esos iterables siempre se probarán como True a menos que hayan sobrecargado especialmente esa operación (como lo hace 'xrange'). –

+0

Ahora funciona para 'iter ([])'. –

3

Puede ser un trabajo para itertools.tee Usted "disparador" de la secuencia en la verificación, sino que se quedan con una copia intacta de la secuencia después:

from itertools import tee 
check, sequence = tee(sequence, 2) 

try: 
    check.next(): 
except StopIteration: 
    #empty sequence 
for item in sequence: 
    #do stuff 

(Vale la pena nting que tee hace lo "correcto" aquí: cargará solo el primer elemento de la secuencia en el momento en que se ejecuta check.next() - y este primer elemento permanecerá disponible en el sequence. Los elementos restantes solo se recuperarán como parte del for bucle O simplemente manteniéndolo simple: Si no puede usar len, no puede verificar si la secuencia tiene un valor bool de True, por las mismas razones.

Por lo tanto, su camino seens bastante simple - otra forma sería la de eliminar el nombre de "punto" antes de la "para" declaración y cheque si es que existe después del bucle:

del item 
for item in sequence: 
    # do stuff 
try: 
    item 
except NameError: 
    # sequence is empty. 

Pero su código se debe utilizar ya que es más claro que esto.

+0

'del item' hará' raise NameError' si el nombre 'item' aún no está definido. –

+0

Usar '' tee'' probablemente no sea el camino a seguir: almacenará en búfer todos los elementos, esperando que también se itere '' check''. Para un iterable de longitud finita, '' list'' puede ser más eficiente, para un iterable de longitud infinita, perderá silenciosamente la memoria. La comprobación de '' NameError'' es brillante, sin embargo. – MisterMiyagi

2

El segundo ejemplo de J.F. Sebastian parece ser el ticket con un ciclo while.

NoItem = object() 
myiter = (x for x in range(10)) 
item = next(myiter, NoItem) 
if item is NoItem: 
    ... 
else: 
    while item is not NoItem: 
     print item 
     item = next(myiter, NoItem) 

No es el más conciso pero objetivamente el más claro ... ¿Barro, no?

1

Esto no debe dar lugar a len():

def handle_items(items): 
    index = -1 
    for index, item in enumerate(items): 
     print 'processing item #%d: %r' % (index, item) 
    # at this point, index will be the offset of the last item, 
    # i.e. length of items minus one 
    if index == -1: 
     print 'there were no items to process' 
    print 'done' 
    print 

# test with an empty generator and two generators of different length: 
handle_items(x for x in()) 
handle_items(x for x in (1,)) 
handle_items(x for x in (1, 2, 3)) 
Cuestiones relacionadas