2010-12-29 8 views
8

Esiste un modo migliore per scrivere il seguente:sintassi for-loop migliore per il rilevamento di sequenze vuote?

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

     counter += 1 

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

Si prega di tenere presente che non posso usare len (iterable_sequence) perché 1) le lunghezze non tutte le sequenze sono note; 2) in alcuni casi chiamare len() può innescare il caricamento degli elementi della sequenza in memoria (come nel caso dei risultati di query sql).

La ragione per cui chiedo è che sono semplicemente curioso di sapere se c'è un modo per rendere più conciso e idiomatico. Quello che sto cercando è lungo le linee di:

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

(supponendo che "altra cosa" qui lavorato solo su sequenze vuote, che so che non lo fa)

+0

un modo migliore per scriverlo - a quale scopo? – canavanin

+0

per renderlo più conciso e dall'aspetto idiomatico. (aggiungerò questo alla mia domanda) –

+0

Un ORM potrebbe rispondere per te; qualcosa come i metodi '.fetchone()' o '.first()' di DB-API restituiscono 'None' se il set di risultati è vuoto (nessuna riga). – jfs

risposta

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 

In ogni caso un articolo viene consumato se è presente.


Un esempio di attuazione empty_adapter() s' menzionato nei commenti:

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

si potrebbe utilizzare come segue:

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

Introdurre caso speciale per una sequenza vuoto per un generale il caso sembra errato. Ci dovrebbe essere una soluzione migliore per un problema specifico del dominio.

+1

Ho bisogno di elaborare tutti gli elementi nell'elenco, non solo il primo. Per elaborare il resto degli elementi, avrò bisogno di impostare un altro ciclo for, che renderà l'intera soluzione ancora meno concisa dell'originale. –

+0

Sembra che consumare un oggetto non sia un'opzione per l'O.P. dato che deve "fare cose" con ogni oggetto. – jsbueno

+0

@Dmitry Beransky: se è necessario consumare tutti gli elementi, è possibile scrivere un adattatore che accetta un iterabile come input e restituisce 'None' se è vuoto o restituisce la stessa sequenza di elementi come input. Per l'implementazione è possibile utilizzare la variante più leggibile tra quelle fornite in questa domanda fino ad ora. – jfs

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

non attivarebbe una chiamata a len()? –

+0

Avrebbe attivato una chiamata a len() per le sequenze già valutate, il che non è un problema - non chiamerà 'len' su altri iterabili - perché in genere non hanno un' len' in ogni caso. Tuttavia, quelli iterabili saranno sempre testati come True a meno che non abbiano sovraccaricato in modo speciale quell'operazione (come 'xrange' fa). –

+0

Ora funziona per 'iter ([])'. –

3

Può essere un lavoro per itertools.tee È "trigger" la sequenza sulla verifica, ma si sono lasciati con una copia intatta della sequenza in seguito:

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

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

(ne vale la pena che la cosa "giusta" è qui: caricherà solo il primo elemento della sequenza nel momento in cui viene eseguito check.next() - e questo primo elemento rimarrà disponibile nello sequence. I restanti elementi verranno recuperati solo come parte del for ciclo O semplicemente mantenendolo semplice: Se non puoi usare len, non puoi controllare se la sequenza ha un valore bool di True, per gli stessi motivi.

Pertanto, il tuo modo seens abbastanza semplice - un altro modo sarebbe quello di eliminare il nome "voce" prima del "per" dichiarazione e verifica se esiste dopo il ciclo:

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

Ma il codice dovrebbe essere usato come più chiaro di questo.

+0

'del item' aumenterà NameError' se il nome' item' non fosse già definito. –

+0

L'uso di '' tee'' probabilmente non è la strada da percorrere: bufferizzerà tutti gli elementi, aspettando che '' check'' sia iterato. Per un iterabile a lunghezza finita, '' list'' può essere più efficiente, per un'infinita lunghezza iterabile che manda in silenzio la memoria. Il controllo di '' NameError'' è brillante, comunque. – MisterMiyagi

2

Il secondo esempio di J.F. Sebastian sembra essere il biglietto 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) 

Non il più conciso ma oggettivamente il più chiaro ... Fango, no?

1

Questo non dovrebbe innescare 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)) 
Problemi correlati