1

I want to create an iterator that counts the length of another iterator while consuming it. Here is a working example of what I want to achieve:

from random import random

def randleniter(p):
    while random() < p:
        yield 7

count = 0

def do():
    def countiter(piter):
        global count
        for i in piter:
            count += 1
            yield i
    list(countiter(randiter(0.99))) #simulator for a different method consuming the iterator
    return count

>>> do()
81

However, I would have never built it like this if I intended to use a global variable. I imagined that since I can do this with nested methods:

def make_adder(x):
    def add(y):
        return x + y
    return add

I would be able to do this:

def do():
    count = 0
    def countiter(piter):
        for i in piter:
            count += 1
            yield i
    list(countiter(randiter(0.99)))
    return count

but this results in a UnboundLocalError: local variable 'count' referenced before assignment. When I print locals() from inside the countiter - it doesn't include count. Can I make countiter access count somehow?

5
  • why not just make a simple class Commented Jun 10, 2014 at 17:34
  • @PadraicCunningham Of course making a simple class would solve it. That's why my question wasn't "how to make this work" - I want to learn in the process :) Commented Jun 10, 2014 at 17:38
  • Obviously, a generator cannot be used in "closure-fashion". Commented Jun 10, 2014 at 17:39
  • Related: stackoverflow.com/questions/9355179/… Commented Jun 10, 2014 at 17:41
  • @Jan-PhilipGehrcke I think it's a duplicate, not related. I've just learned exactly what closure is, so I didn't know to search for that keyword. (According to a definition I found, this isn't even really closure, since the enclosing scope didn't finish its execution) Commented Jun 10, 2014 at 17:46

1 Answer 1

3

what you are describing is known as a closure, which is a topic completely independant from iterators and generators. Python3.x has the nonlocal keyword for that (just declare nonlocal count in countiter to match your desired behaviour, in python 2.7, you have to emulate this via mutable objects (since inner function can read and mutate outer functions variables, just not assign to them).

so you can in fact do:

def do():
    count = [0,]
    def countiter(iter):
        for i in iter:
            count[0] += 1
            yield i
    list(countiter(randiter(0.99)))
    return count[0]
Sign up to request clarification or add additional context in comments.

1 Comment

The code you suggested in your edit is exactly what I ran before accepting the answer. Thanks!

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.