Quick Tip: Understanding the Yield Keyword in Python
Thanks to Shaumik Daityari for kindly helping to peer review this article.
When we call a function in Python, the function will normally start working until it encounters a return
, exception
, or reaches its end – after which it returns control back to the caller. Whenever you call that function again, the process will start from scratch!
Say that you asked a person to track the red cars on the road. The person will keep getting a question asking them if they spotted a red car or not, and the person in turn would answer with either ‘yes’ or ‘no’. If the person answered ‘yes’, the number of times the red car was spotted will increase.
Let’s see how we can do this in Python:
import time
def red_cars(answer):
n = 0
while True:
if answer == 'yes':
n = n + 1
return n
else:
return n
stop = time.time() + 5 * 60
while time.time() < stop:
answer = raw_input('Did you spot a red car on the road? ("yes" or "no"): ')
times = red_cars(answer)
print 'You have spotted ' + str(times) + ' cars so far!'
If you run the program, what do you notice? Did you notice that the number of times for the ‘yes’ answer is always capped at 1
, and when you answer ‘no’ the number of times gets 0
regardless of answering ‘yes’ before?
Here is where Python’s yield
keyword comes into play. yield
is a means by which we temporarily hand control to the caller, and expect to continue from the point at which control has been handed over.
Before giving the solution to the above example, let me demonstrate a very simple example to better illustrate how yield
works.
Say we have the following simple Python script:
def step_by_step():
return 'step 1'
return 'step 2'
return 'step 3'
step = step_by_step()
for i in range (3):
print step
If you run the script, you will get the following output:
step 1
step 1
step 1
Now, if we use yield
instead, as follows:
def step_by_step():
yield 'step 1'
yield 'step 2'
yield 'step 3'
step = step_by_step()
for i in range (3):
print step.next()
The output would be as follows:
step 1
step 2
step 3
As you can see, we were able to create a series of values, as for each call the function continues from the point where it yields a value. This type of function is called a generator. Such function creates a generator iterator, as with each call to the method next()
we move to the next yield
statement.
If we come back to our main example (red cars), it can be written as follows to perform the required task:
import time
def red_cars(answer = None):
n = 0
while True:
if answer=="yes":
n = n + 1
answer = yield n
else:
answer = yield n
car_color = red_cars()
car_color.next()
stop = time.time() + 5 * 60
while time.time() < stop:
answer = raw_input('Did you spot a red car on the road? ("yes" or "no"): ')
print 'You have spotted ' + str(car_color.send(answer)) + ' cars so far!'
Thus, as we can see, yield
is deemed important when we are interested in resuming execution at the last point where the function (generator) exited, and where we are also interested in keeping the values of local variables between the different calls – unlike normal functions, where such values are destroyed when exiting the function.
There are, however, other uses of yield
. For instance, you can use yield
if you have a function which returns a sequence (for example, rows in an excel sheet) and you need to iterate over the sequence without having each value in memory at once. That is, to save memory.
yield
can also be used when working with iterables, where we have a large list that is difficult to pass between functions. For instance, Python’s inbuilt functions for permutations and combinations in the itertools module use yield
.