1

I sometimes use generators to filter for certain values in programs and want to log the filtered items.
Let's assume:

def filter_items(items):
    for item in items:
        if item.is_wanted():
            yield item

def process_items(items):
    for item in filter_items(items):
        item.do_stuff()

Now my problem is that I want to log, how many of the filtered items were actually invoked.
Currently I do this:

def process_items(items):
    for count, item in enumerate(filter_items(items)):
        item.do_stuff()

    try:
        count += 1
    except UnboundLocalError:
        count = 0

    print('Processed', count, 'items.')

Now I have the feeling, that checking for an UnboundLocalError is a bit weird, so I considered defaulting the counter instead:

def process_items(items):
    count = -1

    for count, item in enumerate(filter_items(items)):
        item.do_stuff()

    print('Processed', count + 1, 'items.')

However also setting the default counter to -1 also looks weird, since the actual default value on no iteration will be 0. However I cannot default it to 0 because then I could not distinguish between the default value (if no element was iterated) or whether one element was iterated over.

Is there a best-practice or guideline regarding the defaulting of loop counters in Python?

2 Answers 2

6

I don't think a best practice exists. What I would do (in order to not initialize to -1 and then need to do count + 1) is set the enumerate's start value to 1:

def process_items(items):
    count = 0    
    for count, item in enumerate(filter_items(items), start=1):
        item.do_stuff()

    print('Processed', count, 'items.')

this makes it clear to me what's going on. (Note that start=1 can just be written 1).

Note that yes, this isn't the most explicit way to do this (see Stefan's answer). Since you do know about the fact that the for loop targets are visible after the loop, you should be ok.

Sign up to request clarification or add additional context in comments.

1 Comment

Learning something new every day. Thank you for the hint with enumerate's start parameter.
4

Not sure why you use enumerate. Can't you just increase the counter for each item?

def process_items(items):
    count = 0
    for item in filter_items(items):
        item.do_stuff()
        count += 1
    print('Processed', count, 'items.')

8 Comments

IMHO, using an enumerate is more pythonic!
@SRC IMHO, it is not.
maybe I am wrong and sorry if I am, but I got this idea from several high voted SO posts. ex - this and this
@SRC Though I like it, what enumerate is doing here is implicit; relying on the fact that count is re-assigned and visible after the for loop ends (something which, although clearly stated in the docs, many people might not know). Being explicit as Stefan is suggesting is probably better.
I see your point @Jim Fasarakis Hilliard I think I understand the logic behind this now. Thanks for clearing it up
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.