13.3. Generator Yield
yield
keyword turns function into generator function
Generators can return (or yield) something:
>>> def run():
... yield 1
>>> def run():
... yield 'something'
Generators can be defined with required and optional parameters just like a regular function:
>>> def run(a, b, c=0):
... yield a + b + c
13.3.1. Problem
Python do not execute code after function returns
In order to return two values from the function you must use
tuple
>>> def run():
... return 1
>>>
>>>
>>> result = run()
>>> print(result)
1
>>> def run():
... return 1
... return 2 # this will not execute
>>>
>>>
>>> result = run()
>>> print(result)
1
>>> def run():
... return 1, 2
>>>
>>>
>>> result = run()
>>> print(result)
(1, 2)
13.3.2. Solution
>>> def run():
... yield 1
... yield 2
>>>
>>>
>>> result = run()
>>> tuple(result)
(1, 2)
13.3.3. Call Generator
Generators are called just like a regular function
The rule with positional and keyword arguments are identical to regular functions
Generators are called just like a regular function:
>>> def run():
... yield 1
>>>
>>>
>>> result = run()
The rule with positional and keyword arguments are identical to regular functions:
>>> def run(a, b, c=0):
... yield a + b + c
>>>
>>>
>>> result = run(1, b=2)
13.3.4. Result Object
Calling a generator will return a
generator object
This object is an iterator
One does not simple get data from generator:
>>> def run():
... yield 1
>>>
>>>
>>> result = run()
>>>
>>> print(result)
<generator object run at 0x...>
13.3.5. Evaluate Eagerly
>>> def run():
... yield 1
... yield 2
... yield 3
>>>
>>> result = run()
>>>
>>> tuple(result)
(1, 2, 3)
13.3.6. Evaluate Lazily
All generators implements
Iterator
protocolIterator
hasobj.__iter__()
method which enable use ofiter(obj)
Iterator
hasobj.__next__()
method which enable use ofnext(obj)
In Order to do so, you need to generate next item using next()
>>> def run():
... yield 1
... yield 2
... yield 3
>>>
>>>
>>> result = run()
>>>
>>> next(result)
1
>>>
>>> next(result)
2
>>>
>>> next(result)
3
>>>
>>> next(result)
Traceback (most recent call last):
StopIteration
13.3.7. Evaluate Loop
>>> def run():
... yield 1
... yield 2
... yield 3
>>>
>>> result = run()
>>>
>>> for x in result:
... print(x)
...
1
2
3
13.3.8. Evaluate Iterator
>>> def run():
... yield 1
... yield 2
... yield 3
>>>
>>> result = run()
>>>
>>> it = iter(result) # result.__iter__()
>>>
>>> try:
... x = next(it) # result.__next__()
... print(x)
...
... x = next(it) # result.__next__()
... print(x)
...
... x = next(it) # result.__next__()
... print(x)
...
... x = next(it) # result.__next__()
... print(x)
... except StopIteration:
... pass
...
1
2
3
13.3.9. Execution
There can be one or many
yield
keywords in a generator functionEach
yield
keyword pauses the function and returns the valueAfter last
yield
raisesStopIteration
Execute code before and after yield:
>>> def run():
... print('one')
... print('two')
... yield 1
... print('three')
... print('four')
... yield 2
>>> result = run()
>>> next(result)
one
two
1
>>> next(result)
three
four
2
>>> next(result)
Traceback (most recent call last):
StopIteration
13.3.10. Yield from a Loop
>>> def run():
... for x in range(0,3):
... yield x
>>>
>>>
>>> result = run()
>>>
>>> next(result)
0
>>> next(result)
1
>>> next(result)
2
>>> next(result)
Traceback (most recent call last):
StopIteration
13.3.11. Yields from Loops
>>> def run():
... for x in range(0, 3):
... yield x
... for y in range(10, 13):
... yield y
>>>
>>>
>>> result = run()
>>>
>>> type(result)
<class 'generator'>
>>>
>>> next(result)
0
>>> next(result)
1
>>> next(result)
2
>>> next(result)
10
>>> next(result)
11
>>> next(result)
12
>>> next(result)
Traceback (most recent call last):
StopIteration
13.3.12. Yield in a Zip Loop
firstnames()
- generator (a stream) returning firstnameslastnames()
- generator (a stream) returning lastnamesfor fname, lname in zip(firstnames(), lastnames())
>>> def firstnames():
... yield 'Mark'
... yield 'Melissa'
... yield 'Rick'
>>>
>>>
>>> def lastnames():
... yield 'Watney'
... yield 'Lewis'
... yield 'Martinez'
>>>
>>>
>>> for fname, lname in zip(firstnames(), lastnames()):
... print(f'{fname=}, {lname=}')
fname='Mark', lname='Watney'
fname='Melissa', lname='Lewis'
fname='Rick', lname='Martinez'
13.3.13. Case Study
range()
implementation using function and generator
>>> def myrange(start, stop, step):
... result = []
... current = start
... while current < stop:
... result.append(current)
... current += step
... return result
>>>
>>> result = myrange(0, 10, 2)
>>> tuple(result)
(0, 2, 4, 6, 8)
>>> def myrange(start, stop, step):
... current = start
... while current < stop:
... yield current
... current += step
>>>
>>> result = myrange(0, 10, 2)
>>> tuple(result)
(0, 2, 4, 6, 8)
13.3.14. Use Case - 1
Function:
>>> def even(data):
... result = []
... for x in data:
... if x % 2 == 0:
... result.append(x)
... return result
>>>
>>>
>>> result = even(range(0,10))
>>>
>>> print(result)
[0, 2, 4, 6, 8]
Generator:
>>> def even(data):
... for x in data:
... if x % 2 == 0:
... yield x
>>>
>>>
>>> result = even(range(0,10))
>>>
>>> print(result)
<generator object even at 0x...>
>>>
>>> list(result)
[0, 2, 4, 6, 8]
13.3.15. Use Case - 2
>>> class User:
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
...
... def __str__(self):
... return f'{self.firstname} {self.lastname}'
>>>
>>>
>>> class Order:
... def __init__(self, product, price):
... self.product = product
... self.price = price
...
... def __str__(self):
... return f'product {self.product} for {self.price} USD'
>>> def users():
... yield User('Mark', 'Watney')
... yield User('Melissa', 'Lewis')
... yield User('Rick', 'Martinez')
>>>
>>>
>>> def orders():
... yield Order(product='A', price=123.45)
... yield Order(product='B', price=123.45)
... yield Order(product='C', price=123.45)
>>> for user, order in zip(users(), orders()):
... print(f'{user} bought {order}')
...
Mark Watney bought product A for 123.45 USD
Melissa Lewis bought product B for 123.45 USD
Rick Martinez bought product C for 123.45 USD
13.3.16. 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 Function Passwd
# - Difficulty: medium
# - Lines: 10
# - Minutes: 5
# %% English
# 1. Split `DATA` by lines and then by colon `:`
# 2. Extract system accounts (users with UID [third field] is less than 1000)
# 3. Return list of system account logins
# 4. Implement solution using function
# 5. Implement solution using generator and `yield` keyword
# 6. Run doctests - all must succeed
# %% Polish
# 1. Podziel `DATA` po liniach a następnie po dwukropku `:`
# 2. Wyciągnij konta systemowe (użytkownicy z UID (trzecie pole) mniejszym niż 1000)
# 3. Zwróć listę loginów użytkowników systemowych
# 4. Zaimplementuj rozwiązanie wykorzystując funkcję
# 5. Zaimplementuj rozwiązanie wykorzystując generator i słowo kluczowe `yield`
# 6. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - `str.splitlines()`
# - `str.split()`
# - unpacking expression
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from inspect import isfunction, isgeneratorfunction
>>> assert isfunction(function)
>>> assert isgeneratorfunction(generator)
>>> list(function(DATA))
['root', 'bin', 'daemon', 'adm', 'shutdown', 'halt', 'nobody', 'sshd']
>>> list(generator(DATA))
['root', 'bin', 'daemon', 'adm', 'shutdown', 'halt', 'nobody', 'sshd']
"""
DATA = """root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
nobody:x:99:99:Nobody:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
mwatney:x:1000:1000:Mark Watney:/home/mwatney:/bin/bash
mlewis:x:1001:1001:Melissa Lewis:/home/mlewis:/bin/bash
rmartinez:x:1002:1002:Rick Martinez:/home/rmartinez:/bin/bash
avogel:x:1003:1003:Alex Vogel:/home/avogel:/bin/bash
bjohanssen:x:1004:1004:Beth Johanssen:/home/bjohanssen:/bin/bash
cbeck:x:1005:1005:Chris Beck:/home/cbeck:/bin/bash"""
# list[str] with usernames when UID [third field] is less than 1000
# type: Callable[[str], list[str]]
def function(data: str):
...
# list[str] with usernames when UID [third field] is less than 1000
# type: Generator
def generator(data: str):
...
# %% 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 Function Range
# - Difficulty: medium
# - Lines: 5
# - Minutes: 5
# %% English
# 1. Write own implementation of a built-in `range()` function
# 2. Define function `myrange` with parameters:
# - parameter `start: int`
# - parameter `stop: int`
# - parameter `step: int`
# 3. Don't validate arguments and assume, that user will:
# - always pass valid type of arguments
# - never give only one argument
# - arguments will be unsigned
# 4. Do not use built-in function `range()`
# 5. Run doctests - all must succeed
# %% Polish
# 1. Zaimplementuj własne rozwiązanie wbudowanej funkcji `range()`
# 2. Zdefiniuj funkcję `myrange` z parametrami:
# - parameter `start: int`
# - parameter `stop: int`
# - parameter `step: int`
# 3. Nie waliduj argumentów i przyjmij, że użytkownik:
# - zawsze poda argumenty poprawnych typów
# - nigdy nie poda tylko jednego argumentu
# - argumenty będą nieujemne
# 4. Nie używaj wbudowanej funkcji `range()`
# 5. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - https://github.com/python/cpython/blob/main/Objects/rangeobject.c
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from inspect import isfunction, isgenerator, isgeneratorfunction
>>> assert isfunction(myrange)
>>> assert isgeneratorfunction(myrange)
>>> assert isgenerator(myrange(0,10))
>>> list(myrange(0, 10, 2))
[0, 2, 4, 6, 8]
>>> list(myrange(0, 5))
[0, 1, 2, 3, 4]
"""
# Write own implementation of a built-in `range()` function
# Define function `myrange` with parameters: `start`, `stop`, `step`
# type: Callable[[int,int,int], list[int]]
def myrange(start, stop, step=1):
...