2018年4月19日 星期四

[ Python 常見問題 ] python generator “send” function purpose?

Source From Here 
Question 
Can someone give me an example of why the "send" function associated with Python generator function exists? I fully understand the yield function. However, the send function is confusing to me. The documentation on this method is convoluted: 
generator.send(value) 
Resumes the execution and “sends” a value into the generator function. The value argument becomes the result of the current yield expression. The send() method returns the next value yielded by the generator, or raises StopIteration if the generator exits without yielding another value.

What does that mean? I thought value was the input to the function? The phrase "The send() method returns the next value yielded by the generator" seems to be also the exact purpose of the yield function; yield returns the next value yielded by the generator... Can someone give me an example of a generator utilizing send that accomplishes something yield cannot? 

How-To 
It's used to send values into a generator that just yielded. Here is an artificial (non-useful) explanatory example. Consider a testing function: 
  1. def double_inputs():  
  2.     while True:  
  3.         x = yield  
  4.         yield x * 2  
Then you can use it this way: 
>>> gen = double_inputs()
>>> gen.next() #run up to the first yield
>>> gen.send(10) #goes into 'x' variable
20
>>> gen.next() #run up to the next yield
>>> gen.send(6) #goes into 'x' again
12
>>> gen.next() #run up to the next yield
>>> gen.send(94.3) #goes into 'x' again
188.5999999999999

As to why it's useful, one of the best use cases I've seen is Twisted's @defer.inlineCallbacks. Essentially it allows you to write a function like this: 
  1. @defer.inlineCallbacks  
  2. def doStuff():  
  3.     result = yield takesTwoSeconds()  
  4.     nextResult = yield takesTenSeconds(result * 10)  
  5.     defer.returnValue(nextResult / 10)  
What happens is that takesTwoSeconds() returns a Deferred, which is a value promising a value will be computed later. Twisted can run the computation in another thread. When the computation is done, it passes it into the deferred, and the value then gets sent back to the doStuff() function. Thus the doStuff() can end up looking more or less like a normal procedural function, except it can be doing all sorts of computations & callbacks etc. The alternative before this functionality would be to do something like: 
  1. def doStuff():  
  2.     returnDeferred = defer.Deferred()  
  3.     def gotNextResult(nextResult):  
  4.         returnDeferred.callback(nextResult / 10)  
  5.     def gotResult(result):  
  6.         takesTenSeconds(result * 10).addCallback(gotNextResult)  
  7.     takesTwoSeconds().addCallback(gotResult)  
  8.     return returnDeferred  
It's a lot more convoluted and unwieldy. Perhaps another good usage is to chain the function(s) from FP (refer to here). Below is a simple example: 
  1. #!/usr/bin/env python  
  2. import functools  
  3. import sys  
  4. import time  
  5.   
  6.   
  7. # https://docs.python.org/2/library/functools.html  
  8. def init_coroutine(func):  
  9.     functools.wraps(func)  
  10.     def init(*args, **kwargs):  
  11.         gen = func(*args, **kwargs)  
  12.         next(gen)  
  13.         return gen  
  14.     return init  
  15.   
  16.   
  17. def dataSrc(alist, target):  
  18.     counter = 0  
  19.     for e in alist:  
  20.         target.send(e)  
  21.         time.sleep(1)  
  22.   
  23.   
  24. @init_coroutine  
  25. def plus2(target):  
  26.     while True:  
  27.         x = yield  
  28.         target.send( x + 2 )  
  29.   
  30.   
  31. @init_coroutine  
  32. def multi2(target):  
  33.     while True:  
  34.         x = yield  
  35.         target.send( x * 2 )  
  36.   
  37. @init_coroutine  
  38. def output():  
  39.     while True:  
  40.         x = yield  
  41.         print("Output: {}".format(x))  
  42.   
  43. print('Output: (x + 2) * 2')  
  44. dataSrc(range(5), plus2(multi2(output())))  
  45.   
  46. print('Output: (x * 2) + 2')  
  47. dataSrc(range(5), multi2(plus2(output())))  
The execution output: 
Output: (x + 2) * 2
Output: 4
Output: 6
Output: 8
Output: 10
Output: 12
Output: (x * 2) + 2
Output: 2
Output: 4
Output: 6
Output: 8
Output: 10


Supplement 
Python generator.send with yield

1 則留言:

  1. I like your blog, I read this blog please update more content on hacking, further check it once at python online course

    回覆刪除

[Linux 常見問題] What's the best way to send a signal to all members of a process group?

Source From  Here   Question   I want to  kill a whole process tree.  What is the best way to do this using any common scripting languages? ...