When we call a function in Python, the function will normally start working until it encounters a
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
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
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