It is lunch time and Sammy Sandwiches food truck pulls in to the parking lot next to your office. A line of people queue up next to the truck. Sammy begins taking orders. The workflow Sammy has been accustomed to has always been to take all the orders first, prepare all the orders, and finally deliver them to the customer.
His customers have always been happy, and never complained about the time it takes to get food. But recently his food truck has become quite popular. He now has a pretty sizable line, and by the time he takes all the orders and prepares all the sandwiches, people are getting hungry!
Sammy had an idea. Take all the orders and then deliver each sandwich as he finishes them. That way at least some people don’t have to wait.
That is great, but that only makes a small difference. Customers who ordered first have to wait for everyone else to order before getting their food. Not bad when there are only a few people in line, but it is not scalable!
Rather than taking everyone’s order first, he decides to take one order, prepare the sandwich and then deliver it — all before taking the next order.
Tada! Sammy has done it. He has become successful by understanding and optimizing his operation.
Great story, but no real food truck would work like Sammy did in the beginning, but this happens in code all the time. Generators save us from being forced into a terrible workflows and opens up the bottlenecks. Just
yield to the tasks and your function will put a pin in the loop, work on an item and move on to the next.
Now that you understand the broad strokes, let’s take a look at some ancillary details.
Generators are also iterators! By adding yield to your function, Python automatically adds the
So you can do
yield a generator, you aren’t just pausing execution to extract a value. You can also use that time to send a value to what is yielding.
This will return
No Customer followed by
13 . This can get confusing, especially if you are just reading the code, and not running it. So try running it!
When you need to send data to your generator function, you can’t have the generator function be part of a loop. Instead assign it to a variable so you can manually handle next/send. (In the above example, I assign the generator to the
Once you have the
action variable, you need to step over to the next yield. This is done with the
next() function. Warning: executing
next(), you will get a
TypeError: can't send non-None value to a just-started generator error.
If you did run the above example, you will notice a
StopIteration exception being thrown. This happens because you have exhausted your generator, and it can’t continue. You can silence this with a try/except or you could also tell
next() to default to none with
next(action, None). Just be aware that if you are using
send() there is no default option, and you will need to use a try/except.
The last thing I wanted to mention was pipelining generators. Let’s take a look at the following example:
This iterates and yields all users, first by employees and then by customers. This works fine to drain our generators, but Python has given us some syntactic sugar.
yield from does the same thing, but in a cleaner, more readable format.
Voila! That is generators! Generators are the basis of many optimizations, including Asyncio! I will cover that at another time, but for now, I hope generators are a little less scary for you.
Skyler Lewis makes no claims to expertise or exactness of the information presented and may not reflect the opinions of the owners or advertisers. Parental guidance isn’t recommended. Any rebroadcast, retransmission, or account of this article, without the express written consent of Major League Baseball, is not prohibited. Use as directed. May cause sleepiness, headaches, backaches, or thoughts of bacon wrapped shrimp.