Tutorial :Add local variable to running generator



Question:

Lately, I tried to set local variables from outside of a running generator. The generator code also should access these variables.

One trouble was, that when accessing the variables, it seamed that the interpreter was thinking it must be a global since the variable was not set in the local scope. But I don't wanted to change the global variables and did also not want to copy the whole global scope to make the variables artificially local.

An other trouble was, that it seams that the dictionaries for locals (and globals?) seamed to be read-only when accessed from outside.

Is there any legal (or at least partial legal way) to introduce locals into a running generator instance?

Edit for clarification:

I don't mean the "send" function. This is of course a neat function, but since I want to set multiple variables with differing names, it is not conveniant for my purposes.


Solution:1

What you may be looking for, is the send method, which allows a value to be sent into a generator. The reference provides an example:

>>> def echo(value=None):  ...     print "Execution starts when 'next()' is called for the first time."  ...     try:  ...         while True:  ...             try:  ...                 value = (yield value)  ...             except Exception, e:  ...                 value = e  ...     finally:  ...         print "Don't forget to clean up when 'close()' is called."  ...  >>> generator = echo(1)  >>> print generator.next()  Execution starts when 'next()' is called for the first time.  1  >>> print generator.next()  None  >>> print generator.send(2)  2  >>> generator.throw(TypeError, "spam")  TypeError('spam',)  >>> generator.close()  Don't forget to clean up when 'close()' is called.  

Let me give an example of my own. (Watch out! The code above is Python 2.6, but below I'll write Python 3; py3k ref):

>>> def amplify(iter, amp=1):  ...     for i in iter:  ...         reply = (yield i * amp)  ...         amp = reply if reply != None else amp   ...   >>> it = amplify(range(10))  >>> next(it)  0  >>> next(it)  1  >>> it.send(3) # 2 * 3 = 6  6  >>> it.send(8) # 3 * 8 = 24  24  >>> next(it) # 4 * 8 = 32  32  

Of course, if your really want to, you can also do this without send. E.g. by encapsulating the generator inside a class (but it's not nearly as elegant!):

>>> class MyIter:  ...     def __init__(self, iter, amp=1):  ...         self.iter = iter  ...         self.amp = amp  ...     def __iter__(self):  ...         for i in self.iter:  ...             yield i * self.amp  ...     def __call__(self):  ...         return iter(self)  ...   >>> iterable = MyIter(range(10))  >>> iterator = iterable()  >>> next(iterator)  0  >>> next(iterator)  1  >>> iterable.amp = 3  >>> next(iterator)  6  >>> iterable.amp = 8  >>> next(iterator)  24  >>> next(iterator)  32  

Update: Alright, now that you have updated your question, let me have another stab at the problem. Perhaps this is what you mean?

>>> def amplify(iter, loc={}):  ...     for i in iter:  ...         yield i * loc.get('amp', 1)  ...   >>> it = amplify(range(10), locals())  >>> next(it)  0  >>> next(it)  1  >>> amp = 3  >>> next(it)  6  >>> amp = 8  >>> next(it)  24  >>> next(it)  32  

Note that locals() should be treated as read-only and is scope dependent. As you can see, you'll need to explicitly pass locals() to the generator. I see no way around this...


Solution:2

locals() always returns a read-only dict. You could create your own "locals" dictionary:

def gen_func():      lcls = {}      for i in range(5):          yield (i, lcls)          print lcls      for (val, lcls) in gen_func():      lcls[val] = val  

Any other mutable structure will also work.


Solution:3

If you want to have a coroutine or a generator that also acts as a sink, you should use the send method, as in Stephan202's answers. If you want to change the runtime behavior by settings various attributes in the generator, there's an old recipe by Raymond Hettinger:

def foo_iter(self):      self.v = "foo"      while True:          yield self.v    enableAttributes(foo_iter)  it = foo_iter()  print it.next()  it.v = "boo"  print it.next()  

This will print:

foo  boo  

It shouldn't be too difficult to convert the enableAttributes function into a proper decorator.


Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »