The difference between the **yield** and the **return** statement in Python is:

**Return**. A function that returns a value is called once. The**return**statement returns a value and exits the function altogether.

**Yield**. A function that yields values, is called repeatedly. The**yield**statement pauses the execution of a function and returns a value. When called again, the function continues execution from the previous**yield**. A function that yields values is known as a**generator**.

I’m sure this raises more questions than it answers.

Let’s go through **yield** in Python in great detail. Our goal is to understand why and when should one use **yield** over the **return** statement in Python.

## Generators in Python

Any function that yields values is known as a generator in Python. And a function that returns values is naturally just a function.

Thus, the question “yield vs return in Python” can be rephrased as “functions vs generators in Python”.

Here is a great illustration of functions vs generators in Python:

A generator function returns a generator object, also known as an *iterator*. The iterator generates one value at a time. It does not store any values. This makes a generator memory-efficient.

For example, you can use a generator to loop through values without storing any of them in memory. Later on, you will find out why and when is this useful.

## How to Replace ‘return’ with ‘yield’ in Python

Replacing return statements with yield statements in Python means you turn a function into a generator.

### Example

Let’s create a **square()** function that squares an input list of numbers. This is a regular function that returns the whole list as a result:

def square(numbers): result = [] for n in numbers: result.append(n ** 2) return result numbers = [1, 2, 3, 4, 5] squared_numbers = square(numbers) print(squared_numbers)

Output:

[1, 4, 9, 16, 25]

Let’s then convert this function into a generator. Instead of storing the squared numbers into a list, you can **yield** values one at a time without storing them:

def square(numbers): for n in numbers: yield n ** 2 numbers = [1, 2, 3, 4, 5] squared_numbers = square(numbers) print(squared_numbers)

Output:

<generator object square at 0x7f685175b510>

Now you no longer get the list of squared numbers. This is because the result **squared_numbers** is a *generator object*.

But how can you access the values then?

Let’s next talk about the **next()** function with which you ask generators to produce values.

### Call the next() Function to Yield Values from a Generator

A generator object doesn’t hold numbers in memory. Instead, it computes and **yields** them one at a time. It does this only when you ask for the next value using the **next()** function.

Let’s ask the generator to compute the first squared number:

print(next(squared_numbers))

Output:

1

Let’s make it compute the rest of the numbers by calling **next()** four more times:

print(next(squared_numbers)) print(next(squared_numbers)) print(next(squared_numbers)) print(next(squared_numbers))

Output:

4 9 16 25

Now the generator has squared all the numbers. If you call **next()** one more time:

print(next(squared_numbers))

This time, an error occurs:

Traceback (most recent call last): File "<string>", line 13, in <module> StopIteration

This error lets you know there are no more numbers to be squared. In other words, the generator is exhausted.

Now you understand how a generator works and how to make it compute values.

### Forget about Calling next() with Generators

Using the** next()** function demonstrates how generators work.

In reality, you don’t need to call the** next()** function.

Instead, you can use a for loop with the same syntax you would use on a list.

The for loop actually calls the** next()** function under the hood.

For instance, let’s repeat the generator example using a for loop:

def square(numbers): for n in numbers: yield n ** 2 numbers = [1, 2, 3, 4, 5] squared_numbers = square(numbers) for n in squared_numbers: print(n)

Result:

1 4 9 16 25

This demonstrates the syntactical power generators have. Even though you do not store the values, you can still use the same for-loop syntax you would use on any other iterable.

## Yield an Infinite Stream of Values

As you now know, the generator object yields one value at a time. It does not store any of those values.

This makes it possible to create an infinite stream of values. You can loop through the infinite stream with the same syntax you would loop a list. This is due to the flexible syntax of generators.

### Example

Let’s create an infinite generator that produces all the numbers up to infinity after a starting point:

def infinite_values(start): current = start while True: yield current current += 1

This generator produces values from **start** to infinity.

Let’s loop through these values (**Warning**. An infinite loop):

infinite_nums = infinite_values(0) for num in infinite_nums: print(num)

As a result, you see an infinite loop that prints values indefinitely:

0 1 2 3 4 5 . . .

But infinite loops are bad, aren’t they?

Yes, they are. But the point is to demonstrate how syntactically it looks as if you were able to loop through an infinite collection of values.

Look at the code—you can literally write `for num in infinite_nums`

and it works! This is all thanks to generators and the fact that they do not store values.

## Yield vs. Return—Runtime Comparison

Let’s perform a runtime comparison between yielding and returning in Python.

In this example, there’s a list of ten numbers and two functions:

- A
**data_list()**function that randomly selects a number from the list`n`

times. - A
**data_generator()**generator that function also randomly selects a number from the list`n`

times.

This code compares the runtimes of using these functions to construct a list of 1 million randomly selected numbers:

import random import timeit from math import floor numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] def data_list(n): result = [] for i in range(n): result.append(random.choice(numbers)) return result def data_generator(n): for i in range(n): yield random.choice(numbers) t_list_start = timeit.default_timer() rand_list = data_list(1_000_000) t_list_end = timeit.default_timer() t_gen_start = timeit.default_timer() rand_gen = data_generator(1_000_000) t_gen_end = timeit.default_timer() t_gen = t_gen_end - t_gen_start t_list = t_list_end - t_list_start print(f"List creation took {t_list} Seconds") print(f"Generator creation took {t_gen} Seconds") print(f"The generator is {floor(t_list / t_gen)} times faster to create")

Result:

List creation took 0.6045370370011369 Seconds Generator creation took 3.48799949279055e-06 Seconds The generator is 173319 times faster to create

This shows how a generator is way faster to **create**. This is because when you create a list, all the numbers have to be stored in memory. But when you use a generator, the numbers aren’t stored anywhere, so it’s lightning-fast to create.

## When Use Yield in Python

Ask yourself, “Do I need multiple items at the same time?”.

If the answer is “No”, use a generator.

Let’s go back to the example of squaring numbers. This function takes a list of numbers, squares them, and returns the list.

def square(numbers): result = [] for n in numbers: result.append(n ** 2) return result

As you only want to print a list of squared numbers, you can use a generator. This is because the numbers do not depend on one another—You can square any number without knowing the next one. Thus there is no need to store all the squared numbers anywhere.

def square(numbers): for n in numbers: yield n ** 2

As another, perhaps more practical example, think about looping through a file with a billion strings (e.g. passwords).

There is no way you can store a billion strings into a single list. In this case, you can use a generator to loop through the strings one by one without storing them.

The best part is that syntactically it looks as if you really stored the values into a list and read them from there.

# You could write something like this for word in billion_strings: check(word)

## Conclusion

The difference between **return** and **yield** in Python is that **return** is used with regular functions and **yield** with generators.

The **return** statement returns a value from the function to its caller. After this, the function scope is exited and everything inside the function is gone.

The **yield** statement in Python turns a function into a generator.

A generator is a memory-efficient function that is called repeatedly to retrieve values one at a time.

The **yield** statement pauses the generator from executing and returns a single value. When the generator is called again, it continues execution from where it paused. This process continues until there are no values left.

A generator does not store any values in memory. Instead, it knows the current value and how to get the next one. This makes a generator memory-efficient to create.

The syntactical benefit of generators is that looping through a generator looks identical to looping through a list.

Using generators is good when you loop through a group of elements and do not need to store them anywhere.

Thanks for reading. Happy coding!