2010-12-29 15 views
8

Czy istnieje lepszy sposób, aby napisać następujące:lepsza składnia pętli for do wykrywania pustych sekwencji?

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

     counter += 1 

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

Proszę pamiętać, że nie mogę używać len (iterable_sequence), ponieważ 1) Nie wszystkie sekwencje znane długości; 2) w niektórych przypadkach wywołanie len() może spowodować załadowanie elementów sekwencji do pamięci (tak jak w przypadku wyników zapytania sql).

Powodem, dla którego pytam, jest to, że jestem po prostu ciekawy, czy istnieje sposób, aby uczynić to bardziej zwięzłym i idiomatycznym. Co szukam jest wzdłuż linii:

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

(zakładając, że „inny” tutaj pracował tylko na pustych sekwencji, co wiem, to nie robi)

+0

lepszy sposób to napisać - w jakim celu? – canavanin

+0

, aby był bardziej zwięzły i idiomatyczny. (mam zamiar dodać to do mojego pytania) –

+0

ORM może odpowiedzieć na to za ciebie; coś takiego jak metody '.fetchone()' lub '.first() DB-API zwracają' Brak', jeśli zestaw wyników jest pusty (brak wierszy). – jfs

Odpowiedz

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

Or

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

W każdym przypadku jedna pozycja jest zużywana, jeśli jest obecna.


Przykładem realizacji empty_adapter() „s wspomniano w komentarzach:

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

Można wykorzystać go w następujący sposób:

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

Przedstawiamy przypadek szczególny dla pustego sekwencji dla ogólnego sprawa wydaje się być nieprawidłowa . Powinno być lepsze rozwiązanie dla problemu specyficznego dla domeny.

+1

Muszę przetworzyć wszystkie elementy na liście, a nie tylko pierwsze. Aby przetworzyć pozostałe elementy, muszę ustawić kolejną pętlę, która sprawi, że całe rozwiązanie będzie jeszcze mniej zwięzłe niż oryginał. –

+0

Wygląda na to, że konsumowanie przedmiotu nie jest opcją dla O.P., ponieważ h e musi "robić rzeczy" z każdym przedmiotem. – jsbueno

+0

@Dmitry Beransky: jeśli potrzebujesz skonsumować wszystkie elementy, możesz napisać adapter, który akceptuje wartość iterowalną jako dane wejściowe, i zwraca "Brak", jeśli jest pusty lub dostarcza tej samej sekwencji elementów, co jej dane wejściowe. Do wdrożenia można użyć najbardziej czytelnego wariantu spośród dotychczasowych w tym pytaniu. – jfs

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

czy to nie wywołałoby wywołania len()? –

+0

Wywołałoby to wywołanie len() dla sekwencji, które są już wycenione, co nie jest problemem - nie będzie wywoływać 'len' na innych iterabelach - ponieważ i tak nie mają na ogół' len'. Jednak te iterables będą zawsze testowane jako True, chyba że specjalnie przeładowały tę operację (jak robi 'xrange'). –

+0

Teraz działa dla 'iter ([])'. –

3

Może to być praca dla itertools.tee You „spustem” sekwencji na weryfikacji, ale pozostaje nietknięte kopii sekwencji później:

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

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

(Warto nting, że tee robi tu "właściwą" rzecz: załaduje tylko pierwszy element sekwencji w momencie wykonania check.next() - i ta pierwsza część pozostanie dostępna w sequence. Pozostałe elementy zostaną odzyskane tylko jako część for pętla Lub po prostu utrzymywanie prostoty: Jeśli nie możesz użyć len, nie możesz sprawdzić, czy sekwencja ma wartość bool True, z tych samych powodów.

Dlatego swój sposób seens dość proste - inny sposób byłoby usunąć nazwę „pozycji” przed „do” oświadczenia i sprawdzenie, czy istnieje ona po pętli:

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

Ale kodzie powinno być używane jako bardziej przejrzyste niż to.

+0

'del item' spowoduje' podniesienie NameError', jeśli nazwa 'item' nie została jeszcze zdefiniowana. –

+0

Używanie '' tee'' prawdopodobnie nie jest rozwiązaniem - buforuje wszystkie elementy, spodziewając się, że '' check'' będzie również iterowane. Dla iteracji o skończonej długości, '' lista'' może być bardziej efektywna, dla nieskończonej długości iterable będzie cicho przeciekać pamięć. Jednak sprawdzenie "NameError'' jest genialne. – MisterMiyagi

2

Drugi przykład z J.F. Sebastiana wydaje się być biletem z pętlą 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) 

Nie najbardziej zwięzły, ale obiektywnie najczystszy ... Błoto, nie?

1

Nie powinno to wywoł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))