Return and yield in the same function

PythonYield

Python Problem Overview


What exactly happens, when yield and return are used in the same function in Python, like this?

def find_all(a_str, sub):
    start = 0
    while True:
        start = a_str.find(sub, start)
        if start == -1: return
        yield start
        start += len(sub) # use start += 1 to find overlapping matches

Is it still a generator?

Python Solutions


Solution 1 - Python

Yes, it' still a generator. The return is (almost) equivalent to raising StopIteration.

PEP 255 spells it out:

> Specification: Return > --------------------- > > A generator function can also contain return statements of the form: > > "return" > > Note that an expression_list is not allowed on return statements in > the body of a generator (although, of course, they may appear in the > bodies of non-generator functions nested within the generator). > > When a return statement is encountered, control proceeds as in any > function return, executing the appropriate finally clauses (if any > exist). Then a StopIteration exception is raised, signalling that the > iterator is exhausted. A StopIteration exception is also raised if > control flows off the end of the generator without an explict return. > > Note that return means "I'm done, and have nothing interesting to > return", for both generator functions and non-generator functions. > > Note that return isn't always equivalent to raising StopIteration: > the difference lies in how enclosing try/except constructs are > treated. For example, > > >>> def f1(): > ... try: > ... return > ... except: > ... yield 1 > >>> print list(f1()) > [] > > because, as in any function, return simply exits, but > > >>> def f2(): > ... try: > ... raise StopIteration > ... except: > ... yield 42 > >>> print list(f2()) > [42] > > because StopIteration is captured by a bare "except", as is any > exception.

Solution 2 - Python

Yes, it is still a generator. An empty return or return None can be used to end a generator function. It is equivalent to raising a StopIteration(see @NPE's answer for details).

Note that a return with non-None arguments is a SyntaxError in Python versions prior to 3.3.

As pointed out by @BrenBarn in comments starting from Python 3.3 the return value is now passed to StopIteration.

From PEP 380:

> In a generator, the statement > > return value > > is semantically equivalent to > > raise StopIteration(value)

Solution 3 - Python

There is a way to accomplish having a yield and return method in a function that allows you to return a value or generator.

It probably is not as clean as you would want but it does do what you expect.

Here's an example:

def six(how_many=None):
    if how_many is None or how_many < 1:
        return None  # returns value

    if how_many == 1:
        return 6  # returns value

    def iter_func():
        for count in range(how_many):
            yield 6
    return iter_func()  # returns generator

Solution 4 - Python

Note: you don't get StopIteration exception with the example below.

def odd(max):
	n = 0
	while n < max:
		yield n
		n = n + 1
	return 'done'


for x in odd(3):
	print(x)

> The for loop catches it. That's its signal to stop

But you can catch it in this way:

g = odd(3)

while True:
	try:
		x = next(g)
		print(x)
	except StopIteration as e:
		print("g return value:", e.value)
		break

       

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionnekomimiView Question on Stackoverflow
Solution 1 - PythonNPEView Answer on Stackoverflow
Solution 2 - PythonAshwini ChaudharyView Answer on Stackoverflow
Solution 3 - PythonWilliam RusnackView Answer on Stackoverflow
Solution 4 - PythonRickView Answer on Stackoverflow