简体   繁体   中英

Trying to create generator object but getting function object that doesn't respond to generator calls

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?

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]

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

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
...
def f(n):
    i = 0
    while True:
        yield i // n
        i += 1

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