Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python 3: send method of generators

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).

I somehow can't catch why the value should become the result of the current yield expression. I prepared an example:

def gen():     for i in range(10):         X = yield i         if X == 'stop':             break         print("Inside the function " + str(X))  m = gen() print("1 Outside the function " + str(next(m)) + '\n') print("2 Outside the function " + str(next(m)) + '\n') print("3 Outside the function " + str(next(m)) + '\n') print("4 Outside the function " + str(next(m)) + '\n') print('\n') print("Outside the function " + str(m.send(None)) + '\n') # Start generator print("Outside the function " + str(m.send(77)) + '\n') print("Outside the function " + str(m.send(88)) + '\n') #print("Outside the function " + str(m.send('stop')) + '\n') print("Outside the function " + str(m.send(99)) + '\n') print("Outside the function " + str(m.send(None)) + '\n') 

The result is:

1 Outside the function 0  Inside the function None 2 Outside the function 1  Inside the function None 3 Outside the function 2  Inside the function None 4 Outside the function 3    Inside the function None Outside the function 4  Inside the function 77 Outside the function 5  Inside the function 88 Outside the function 6  Inside the function 99 Outside the function 7  Inside the function None Outside the function 8 

Well, frankly speaking, it is astonishing me.

  1. In the documentation we can read that when a yield statement is executed, the state of the generator is frozen and the value of expression_list is returned to next‘s caller. Well, it doesn't seem to have happened. Why can we execute if statement and print function inside gen().
  2. How can I understand why X inside and outside the function differs? Ok. Let us assume that send(77) transmits 77 into m. Well, yield expression becomes 77. Then what is X = yield i? And how 77 inside the function converts into 5 when occurs outside?
  3. Why the first result string doesn't reflect anything that is going on inside the generator?

Anyway, could you somehow comment on these send and yield statements?

like image 390
Kifsif Avatar asked Sep 28 '12 09:09

Kifsif


People also ask

How do you send value to a generator?

Sending Values to GeneratorsThe send() method resumes the generator and sends a value that will be used to continue with the next yield . The method returns the new value yielded by the generator. The syntax is send() or send(value) . Without any value, the send method is equivalent to a next() call.

What is send in Python?

The send() method returns the next value yielded by the generator, or raises StopIteration if the generator exits without yielding another value. When send() is called to start the generator, it must be called with None as the argument, because there is no yield expression that could receive the value.

Is Python 3 range a generator?

So no, range is not a generator. They are immutable, so they can be used as dictionary keys. They have the start , stop and step attributes (since Python 3.3), count and index methods and they support in , len and __getitem__ operations. You can iterate over the same range multiple times.

How do you return a generator in Python?

yield in Python can be used like the return statement in a function. When done so, the function instead of returning the output, it returns a generator that can be iterated upon. You can then iterate through the generator to extract items. Iterating is done using a for loop or simply using the next() function.


1 Answers

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.

like image 89
ecatmur Avatar answered Sep 16 '22 17:09

ecatmur