Python 3: send method of generators

后端 未结 4 1179
天涯浪人
天涯浪人 2020-12-12 14:37

I can\'t understand the send method. I understand that it is used to operate the generator. But the syntax is here: generator.send(value).

相关标签:
4条回答
  • def gen():
        i = 1
        while True:
            i += 1
            x = yield i
            print(x)
    
    m = gen()
    next(m)
    next(m)
    m.send(4)
    

    result

    None
    4
    

    look at more simplified codes above.
    I think the thing leaded to your confusion is 'x = yield i' statment, this statment is not saying value accepted from send() method assgined to i then i assgined to x. Instead, value i is returned by yield statment to generator, x is assgined by send() method.One statement does two thing at same time.

    0 讨论(0)
  • 2020-12-12 15:02

    The most confusing part should be this line X = yield i, specially when you call send() on the generator. Actually the only thing you need to know is:

    in the lexical level: next() is equal to send(None)

    in the interpreter level: X = yield i equals to below lines(ORDER MATTERS):

    yield i
    # won't continue until next() or send() is called
    # and this is also the entry point of next() or send()
    X = the_input_of_send
    

    and, the 2 lines of comment is the exact reason, why we need to call send(None) for the first time, because the generator will return i (yield i) before assign the value to X

    0 讨论(0)
  • 2020-12-12 15:07

    When you use send and expression yield in a generator, you're treating it as a coroutine; a separate thread of execution that can run sequentially interleaved but not in parallel with its caller.

    When the caller executes R = m.send(a), it puts the object a into the generator's input slot, transfers control to the generator, and waits for a response. The generator receives object a as the result of X = yield i, and runs until it hits another yield expression e.g. Y = yield j. Then it puts j into its output slot, transfers control back to the caller, and waits until it gets resumed again. The caller receives j as the result of R = m.send(a), and runs until it hits another S = m.send(b) statement, and so on.

    R = next(m) is just the same as R = m.send(None); it's putting None into the generator's input slot, so if the generator checks the result of X = yield i then X will be None.

    As a metaphor, consider a dumb waiter:

    Dumb waiter

    When the server gets an order from a customer, they put the pad in the dumb waiter, send it to the kitchen, and wait by the hatch for the dish:

    R = kitchen.send("Ham omelette, side salad")
    

    The chef (who's been waiting by the hatch) picks up the order, prepares the dish, yields it to the restaurant, and waits for the next order:

    next_order = yield [HamOmelette(), SideSalad()]
    

    The server (who's been waiting by the hatch) takes the dish to the customer and returns with another order, etc.

    Because both the server and chef wait by the hatch after sending an order or yielding a dish, there's only one person doing anything at any one time i.e. the process is single threaded. Both sides can use normal control flow, as the generator machinery (the dumb waiter) takes care of interleaving execution.

    0 讨论(0)
  • 2020-12-12 15:22

    Since you asked even for comments, consider the following case:

    def lambda_maker():
        def generator():
            value = None
            while 1:
                value = yield value
                value= value[0][1]
        f = generator()
        next(f)  # skip the first None
        return f.send  # a handy lambda value: value[0][1]
    

    Now the following two lines are equivalent:

    a_list.sort(key=lambda a: a[0][1])
    a_list.sort(key=lambda_maker())
    

    (Incidentally, in the current (2018-05-26, day 1 post-GDPR ☺) CPython2 and CPython3 implementations, the second line runs faster than the first, but that's a detail related to frame object initialization overhead on every function call.)

    What happens here? lambda_maker calls f=generator() and gets a generator; calling the initial next(f) starts running the generator and consumes the initial None value, and pauses at the yield line. Then it returns the bound method f.send to its caller. From this point on, every time this bound method is called, the generator.value local receives the argument of the bound method, recalculates value and then loops back yielding the current value of value and waits for the next .send to get another value.

    The generator object stays in-memory and all it does in the loop is:

    • yield the current result (initially None)
    • receive another value (whatever someone used as argument to .send)
    • re-calculate current result based on received value
    • loop back
    0 讨论(0)
提交回复
热议问题