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

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...