2

I'm trying to write an infinite generator that will repeat every positive integer n times. So for example, if I create f = inf_repeat(3), printing the output of f 10 times would result in:

1 1 1 2 2 2 3 3 3 4

I am close but not quite there. Here's what I've got:

    # courtesy of http://stackoverflow.com/questions/279561/what-is-the-python-equivalent-of-static-variables-inside-a-function
    # a generator that yields items instead of returning a list
    def static_var(varname, value):
        def decorate(func):
            setattr(func, varname, value)
            return func
        return decorate

    def inf_repeat(k):
        count_limit = k
        @static_var("counter", 0)
        @static_var("number", 0)
        def func():
            while True:
                if  func.counter == count_limit:
                    func.counter = 0
                    func.number += 1
                func.counter += 1
                yield func.number
        return func

My problem is that this doesn't behave entirely like an iterator. The following commands work:

f3 = inf_repeat(3)
print next(f3())

But it's irritating to have to call f3 with parens. I'd like to be able to use the standard iterator syntax I've seen, such as:

print(f3.next())

and

new_list = [iter(f3)]*5

What do I need to modify in my function to get to that point? Looking at a variety of generator tutorials, it seemed that yield was sufficient to create a generator, but clearly that's not the case.

Also I have no objective to using a module. I checked itertools but maybe I missed something that could do what I want without all this code?

sunny
  • 3,853
  • 5
  • 32
  • 62

4 Answers4

1

You just need to call the generator object (what you called f3) at some point. You can call it when you create it:

f3 = inf_repeat(3)()

or even inside inf_repeat

# change last line to this
return func()

Putting yield in your function makes it a generator function --- that is, a function that, when called, returns a generator. If you want to get the generator, you need to call your generator function at some point.

Incidentally, your implementation is needlessly complex. You can get your desired behavior much more simply without all those decorators and nested functions:

def inf_repeat(k):
    number = k
    while True:
        yield number // k
        number += 1

Then:

>>> list(itertools.islice(inf_repeat(3), 10))
[1, 1, 1, 2, 2, 2, 3, 3, 3, 4]
>>> list(itertools.islice(inf_repeat(4), 13))
[1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4]
BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • thanks a lot. Also thanks for your reworked example. For some reason I was worried about different generators sharing properties, but you showed me how a generator is just a function. Each one is its own function call so I don't need to worry about overlap, apparently? Will need to read up on this more. – sunny Sep 25 '15 at 20:08
  • @sunny: Right, if you call `inf_repeat` multiple times, the result of each call is a separate generator with its own state (i.e., its own 'static" local variables). It won't share state with anything else unless it's referencing global variables or external objects you pass in as arguments or something like that. – BrenBarn Sep 25 '15 at 20:09
1

Here's a simple solution:

def inf_repeat(N):
    i = 1
    while True:
        for n in range(N):
            yield i
        i += 1

# Testing:
f = inf_repeat(3)
for j in range(10):
    print f.next()

Here's a solution using itertools:

def inf_repeat(N):
    return chain.from_iterable(repeat(i, N) for i in count(1))
Alex Hall
  • 34,833
  • 5
  • 57
  • 89
  • Why not use `for f in inf_repeat(3): print f`? – MrAlexBailey Sep 25 '15 at 20:02
  • @Jkdc: Because `inf_repeat` is infinite, so that would be an infinite loop. – BrenBarn Sep 25 '15 at 20:03
  • That should work but the program will never end and I'm making it clear that OP can call `.next()` as desired. – Alex Hall Sep 25 '15 at 20:04
  • @AlexHall thanks a lot for showing me this. I knew that I must be doing too much, and now it seems so obvious seeing this simple example. I got all into thinking I'd have some sort of shared object problem, but now I see a generator is more like spinning off a function even when you don't do it explicitly. – sunny Sep 25 '15 at 20:07
1

Another solution using itertools

import itertools as it

def inf_repeat(k):
    for i in it.count(1):
        for j in [i]*k:
            yield j

for n in inf_repeat(3): print n

produces

1
1
1
2
2
2
...
Pynchia
  • 10,996
  • 5
  • 34
  • 43
1
def f(n):
    i = 0
    while True:
        yield i // n
        i += 1
acushner
  • 9,595
  • 1
  • 34
  • 34