basic generator question

Peter Otten __peter__ at web.de
Wed Feb 4 11:02:24 EST 2015


Neal Becker wrote:

> I have an object that expects to call a callable to get a value:
> 
> class obj:
>   def __init__ (self, gen):
>     self.gen = gen
>   def __call__ (self):
>     return self.gen()
 
As written that looks a bit like

if boolean_expression == True: ...

as you could replace

inst = obj(callable)

with 

inst = callable

but that may be an artifact of the example.

> Now I want gen to be a callable that repeats N times.  I'm thinking, this
> sounds perfect for yield
> 
> class rpt:
>   def __init__ (self, value, rpt):
>     self.value = value; self.rpt = rpt
>   def __call__ (self):
>     for i in range (self.rpt):
>       yield self.value
> 
> so I would do:
> 
> my_rpt_obj = obj (rpt ('hello', 5))
> 
> to repeat 'hello' 5 times (for example).

What do you expect to happen when my_rpt_obj is called the sixth time?
 
> But this doesn't work.  when obj calls self.gen(), that returns a
> generator, not the next value.
> 
> How can I make this work?  I can't change the interface of the existing
> class obj, which expects a callable to get the next value.

>>> class Obj:
...     def __init__(self, gen):
...         self.gen = iter(gen)
...     def __call__(self):
...         return next(self.gen)
... 
>>> class Repeat:
...     def __init__(self, value, times):
...         self.value = value
...         self.times = times
...     def __iter__(self):
...         for i in range(self.times):
...             yield self.value
... 
>>> r = Obj(Repeat("hello", 3))
>>> r()
'hello'
>>> r()
'hello'
>>> r()
'hello'
>>> r()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __call__
StopIteration

Instead of the Repeat class you may use a generator:

>>> def repeat(value, times):
...     for i in range(times):
...         yield value
... 
>>> r = Obj(repeat("hello", 2))
>>> r()
'hello'
>>> r()
'hello'
>>> r()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __call__
StopIteration

This is for demonstration purposes as there is already itertools.repeat():

>>> import itertools
>>> r = Obj(itertools.repeat("world", 2))
>>> r()
'world'
>>> r()
'world'
>>> r()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __call__
StopIteration

Depending on your actual need you may also omit the Obj() class:

>>> import functools
>>> r = functools.partial(next, itertools.repeat("goodbye", 2))
>>> r()
'goodbye'
>>> r()
'goodbye'
>>> r()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Somewhat less formal you can bind the iterator method directly:

>>> r = itertools.repeat("GOODBYE", 2).__next__ # next in Python 2
>>> r()
'GOODBYE'
>>> r()
'GOODBYE'
>>> r()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration





More information about the Python-list mailing list