简体   繁体   中英

better for-loop syntax for detecting empty sequences?

Is there a better way to write the following:

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

      counter += 1

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

Please keep in mind that I can't use len(iterable_sequence) because 1) not all sequences have known lengths; 2) in some cases calling len() may trigger loading of the sequence's items into memory (as the case would be with sql query results).

The reason I ask is that I'm simply curious if there is a way to make above more concise and idiomatic. What I'm looking for is along the lines of:

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

(assuming "else" here worked only on empty sequences, which I know it doesn't)

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   

In each case one item is consumed if it is present.


An example of empty_adapter() 's implementation mentioned in the comments:

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

You could use it as follows:

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

Introducing special case for an empty sequence for a general case seems wrong . There should be a better solution for a domain specific problem.

It may be a job for itertools.tee You "trigger" the sequence on the verification, but you are left with an untouched copy of the sequence afterwards:

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

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

(it's worth nting that tee does the "right" thing here: it will load just the first element of the sequence in the moment check.next() is executed - and this first elment will remain available in the sequence . The remaining items will only be retrieved as part of the for loop Or just keeping it simple: If you can't use len, you can't check if the sequence has a bool value of True, for the same reasons.

Therefore, your way seens simple enough - another way would be to delete the name "item" before the "for" statement and check if it exists after the loop:

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

But your code should be used as its more clear than this.

The second example from JF Sebastian seems to be the ticket with a while loop.

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)

Not the most concise but objectively the clearest... Mud, no?

This shouldn't trigger 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))
if not iterable_sequence.__length_hint__():
    empty()
else:
    for item in iterable_sequence:
        dostuff()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM