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?

Richard Neumann
  • 2,986
  • 2
  • 25
  • 50

2 Answers2

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.

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
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.')
Stefan Pochmann
  • 27,593
  • 8
  • 44
  • 107
  • 1
    IMHO, using an enumerate is more pythonic! – SRC Oct 06 '17 at 08:44
  • @SRC IMHO, it is not. – Stefan Pochmann Oct 06 '17 at 08:45
  • maybe I am wrong and sorry if I am, but I got this idea from several high voted SO posts. ex - [this](https://stackoverflow.com/questions/3162271/get-loop-count-inside-a-python-for-loop) and [this](https://stackoverflow.com/questions/1185545/python-loop-counter-in-a-for-loop) – SRC Oct 06 '17 at 08:48
  • 2
    @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. – Dimitris Fasarakis Hilliard Oct 06 '17 at 08:48
  • I see your point @Jim Fasarakis Hilliard I think I understand the logic behind this now. Thanks for clearing it up – SRC Oct 06 '17 at 08:50
  • @SRC Those other SO questions you linked to are different. There the counter is used **inside** the loop. In that case I do agree that `enumerate` is better. But here we just want it **after** the loop, and I feel `enumerate` is less appropriate for that, for the reason Jim explained. – Stefan Pochmann Oct 06 '17 at 08:53
  • @SRC And because the combination of `count = 0` and `count += 1` feels "natural" to me but the combination of `count = 0` and the `enumerate` thing feels "unnatural" to me. Can't really explain why, might just be experience, i.e., that I'm used to the first combination and not used to the second. – Stefan Pochmann Oct 06 '17 at 08:59
  • Simple and straightforward. I honestly did not consider it and was blinded by `enumerate`. However I'll most likely go with @JimFasarakisHilliard's suggestion. – Richard Neumann Oct 06 '17 at 09:02