13.5. Generator Yield From
Since Python 3.3: PEP 380 -- Syntax for Delegating to a Subgenerator
Helps with refactoring generators
Useful for large generators which can be split into smaller ones
Delegation call
yield from
terminates onGeneratorExit
from other functionThe value of the
yield from
expression is the first argument to theStopIteration
exception raised by the iterator when it terminatesReturn expr in a generator causes
StopIteration(expr)
to be raised upon exit from the generator
>>> def run():
... yield from generator1()
... yield from generator2()
13.5.1. Problem
>>> def run():
... # firstnames
... yield 'Mark'
... yield 'Melissa'
... yield 'Rick'
...
... # lastnames
... yield 'Watney'
... yield 'Lewis'
... yield 'Martinez'
13.5.2. Solution
yield from
>>> def firstnames():
... yield 'Mark'
... yield 'Melissa'
... yield 'Rick'
>>>
>>> def lastnames():
... yield 'Watney'
... yield 'Lewis'
... yield 'Martinez'
>>>
>>> def run():
... yield from firstnames()
... yield from lastnames()
13.5.3. Rationale
>>> def firstnames():
... yield 'Mark'
... yield 'Melissa'
... yield 'Rick'
>>>
>>> def lastnames():
... yield 'Watney'
... yield 'Lewis'
... yield 'Martinez'
This will not work at all! Mind that no code is executed by a function
after the return
keyword.
>>> def run():
... return firstnames(), lastnames()
This will yield generators (not their values):
>>> def run():
... yield firstnames()
... yield lastnames()
13.5.4. Execute
>>> def firstnames():
... yield 'Mark'
... yield 'Melissa'
... yield 'Rick'
>>>
>>> def lastnames():
... yield 'Watney'
... yield 'Lewis'
... yield 'Martinez'
>>>
>>> def run():
... yield from firstnames()
... yield from lastnames()
>>>
>>>
>>> result = run()
>>>
>>> type(result)
<class 'generator'>
>>>
>>> next(result)
'Mark'
>>>
>>> next(result)
'Melissa'
>>>
>>> next(result)
'Rick'
>>>
>>> next(result)
'Watney'
>>>
>>> next(result)
'Lewis'
>>>
>>> next(result)
'Martinez'
>>>
>>> next(result)
Traceback (most recent call last):
StopIteration
13.5.5. Itertools Chain
The code is equivalent to itertools.chain()
:
>>> from itertools import chain
>>>
>>>
>>> def firstnames():
... yield 'Mark'
... yield 'Melissa'
... yield 'Rick'
>>>
>>> def lastnames():
... yield 'Watney'
... yield 'Lewis'
... yield 'Martinez'
>>>
>>> result = chain(firstnames(), lastnames())
>>>
>>> type(result)
<class 'itertools.chain'>
>>>
>>> next(result)
'Mark'
>>>
>>> next(result)
'Melissa'
>>>
>>> next(result)
'Rick'
>>>
>>> next(result)
'Watney'
>>>
>>> next(result)
'Lewis'
>>>
>>> next(result)
'Martinez'
>>>
>>> next(result)
Traceback (most recent call last):
StopIteration
13.5.6. Yield From Sequences
>>> def run():
... yield from [0, 1, 2]
>>>
>>>
>>> result = run()
>>>
>>> type(result)
<class 'generator'>
>>>
>>> next(result)
0
>>> next(result)
1
>>> next(result)
2
>>> next(result)
Traceback (most recent call last):
StopIteration
13.5.7. Yield From Comprehensions
>>> def run():
... yield from [x for x in range(0,3)]
>>>
>>>
>>> result = run()
>>>
>>> type(result)
<class 'generator'>
>>>
>>> next(result)
0
>>> next(result)
1
>>> next(result)
2
>>> next(result)
Traceback (most recent call last):
StopIteration
13.5.8. Yield From Functions
Delegation call
yield from
turns ordinary function, into a delegation call:
>>> def as_float(data):
... result = []
... for x in data:
... result.append(float(x))
... return result
>>>
>>>
>>> def run():
... data = (1, 2, 3)
... yield from as_float(data)
>>>
>>>
>>> result = run()
>>>
>>> next(result)
1.0
>>>
>>> next(result)
2.0
>>>
>>> next(result)
3.0
>>>
>>> next(result)
Traceback (most recent call last):
StopIteration
13.5.9. Conclusion
Python yield keyword creates a generator function.
It's useful when the function returns a large amount of data by splitting it into multiple chunks.
We can also send values to the generator using its
send()
function.The
yield from
statement is used to create a sub-iterator from the generator function.
13.5.10. Use Case - 1
>>> from pathlib import Path
>>>
>>>
>>> def get_files(dir):
... yield from dir.rglob('*.md')
... yield from dir.rglob('*.rst')
... yield from dir.rglob('*.txt')
>>>
>>>
>>> directory = Path('/tmp/myproject')
>>> files = get_files(directory)
>>>
>>> for file in files:
... print(file)
13.5.11. Use Case - 2
>>> import re
>>>
>>>
>>> DATA = """
... +1 (234) 567-8910
... +1 234 567 8910
... +1 234-567-8910
... +1-234-567-8910
... """
>>>
>>> def get_phone_number(DATA, pattern=r'\+\d{1,4}\s\d{3}\s\d{3}\s\d{3,4}'):
... yield from re.finditer(pattern, DATA)
>>>
>>> for number in get_phone_number(DATA):
... print(number.group())
...
+1 234 567 8910
13.5.12. Assignments
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: Generator YieldFrom Path
# - Difficulty: easy
# - Lines: 2
# - Minutes: 3
# %% English
# 1. Create function `get_files()`
# 2. Using `Pathlib.glob()` return files in `path: Path`:
# - Python files (extension: `*.py`)
# - ReST files (extension: `*.rst`)
# 3. Return result as `Iterator[Path]` using `yield from`
# %% Polish
# 1. Stwórz funkcję `get_files()`
# 2. Używając `Pathlib.glob()` zwróć pliki w katalogu `path: Path`:
# - Pliki Python (rozszerzenie: `*.py`)
# - Pliki ReST (rozszerzenie: `*.rst`)
# 3. Zwróć wyniki jako `Iterator[Path]` używając `yield from`
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from inspect import isgenerator, isgeneratorfunction
>>> path = Path.cwd()
>>> file = get_files(path)
>>> assert isgeneratorfunction(get_files)
>>> assert isgenerator(file)
>>> assert isinstance(next(file), Path)
"""
from pathlib import Path
from typing import Iterator
def get_files(path: Path) -> Iterator[Path]:
...