views:

135

answers:

4

Is there way to stop yielding when generator did not finish values and all needed results have been read? I mean that generator is giving values without ever doing StopIteration.

For example, this never stops: (REVISED)

from random import randint
def devtrue():
    while True:
        yield True

answers=[False for _ in range(randint(100,100000))]
answers[::randint(3,19)]=devtrue()
print answers

I found this code, but do not yet understand, how to apply it in this case: http://code.activestate.com/recipes/576585-lazy-recursive-generator-function/

+8  A: 

You can call close() on the generator object. This way, a GeneratorExit exception is raised within the generator and further calls to its next() method will raise StopIteration:

>>> def test():
...     while True:
...         yield True
... 
>>> gen = test()
>>> gen
<generator object test at ...>
>>> gen.next()
True
>>> gen.close()
>>> gen.next()
Traceback (most recent call last):
  ...
StopIteration
Ferdinand Beyer
A: 

As you have already seen,

TypeError: 'generator' object is unsubscriptable

And the way you have written devtrue it shouldn't stop. If you need that capacity you could:

def bounded_true(count)
   while count > 0:
       yield True
       count -= 1

or far more simply:

y = [True] * 5

If you make an infinite generator, it will generate infinitely.

msw
It should not be impossible, see for example /dev/random pseudofile. Maybe we need generatorIO package in addition to stringIO?
Tony Veijalainen
It's not impossible. You wrote an infinite generator, and got what you asked for. I gave you an example which I just simplified for clarity.
msw
The problem is not the generator, which is what I wanted: generator always giving same value. I would need only that the slice acted as iterator (list is iterable you know) and did the StopIteration at the end of slice.
Tony Veijalainen
A: 

This is best I came up with, but it does still the slicing twice to find the length and need to convert string number from splitting to int:

from time import clock
from random import randint
a=[True for _ in range(randint(1000000,10000000))]
spacing=randint(3,101)
t=clock()
try:
    a[::spacing]=[False]
except ValueError as e:
    a[::spacing]=[False]*int(e.message.rsplit(' ',1)[-1])

print spacing,clock()-t

# baseline

t=clock()
a[::spacing]=[False]*len(a[::spacing])
print 'Baseline:',spacing,clock()-t

I will try it to my prime sieve, but it is likely not to be faster than doing the length arithmetic from recurrence formula. Improving pure Python prime sieve by recurrence formula

Tony Veijalainen
A: 

In analogy with the take function in Haskell, you can build a 'limited' generator based upon another generator:

def take(n,gen):
    '''borrowed concept from functional languages'''
togo=n
while togo > 0:
    yield gen.next()
    togo = togo - 1

def naturalnumbers():
    ''' an unlimited series of numbers '''
    i=0
    while True:
        yield i
        i=i+1

for n in take(10, naturalnumbers() ):
   print n

You can further this idea with an "until" generator, a "while", ...

def gen_until( condition, gen ):
   g=gen.next()
   while( not condition(g) ):
      yield g
      g=gen.next()

And use it like

for i in gen_until( lambda x: x*x>100, naturalnumbers() ):
  print i

...

xtofl
Why use self-defined take instead of islice? For me general incrementer is enough, natural numbers is special case with start value 0. Would be interesting though if generators would have access to previous value generated without needing to explicitly set variable with result value (little like _ variable in command line of Python).
Tony Veijalainen
@Tony Veijalainen: I probably misread your question. I thought you needed a way to limit the number of yielded results, regardless of stopping the generator expliclitly. On top of that, I haven't yet reached a good knowledge of itertools. Thanks for pointing that out!
xtofl
You maybe can teach me trick or two if you have experience in Haskell.
Tony Veijalainen
@xtofl: Have a look at `itertools.takewhile`
Daenyth