5.3. Generator Function

  • yield keyword turns function into generator function

5.3.1. Recap

>>> 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)

5.3.2. Example

  • 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)
>>> 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)
>>> list(result)
[0, 2, 4, 6, 8]

5.3.3. Definition

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

5.3.4. Call Generator

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)

5.3.5. Get Results

  • All generators implements Iterator protocol

  • Iterator has obj.__iter__() method which enable use of iter(obj)

  • Iterator has obj.__next__() method which enable use of next(obj)

One does not simple get data from generator:

>>> def run():
...     yield 1
>>>
>>>
>>> result = run()
>>>
>>> print(result)  
<generator object run at 0x...>

In Order to do so, you need to generate next item using next()

>>> def run():
...     yield 1
>>>
>>>
>>> result = run()
>>>
>>> next(result)
1
>>> next(result)
Traceback (most recent call last):
StopIteration

5.3.6. Yield Keyword

>>> def run():
...     yield 1
>>>
>>>
>>> result = run()
>>>
>>> next(result)
1
>>> next(result)
Traceback (most recent call last):
StopIteration
>>> 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
>>> def run():
...     print('a')
...     print('aa')
...     yield 1
...     print('b')
...     print('bb')
...     yield 2
...     print('c')
...     print('cc')
...     yield 3
>>>
>>>
>>> result = run()
>>>
>>> next(result)
a
aa
1
>>> next(result)
b
bb
2
>>> next(result)
c
cc
3
>>> next(result)
Traceback (most recent call last):
StopIteration

5.3.7. Yield in 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

5.3.8. Yields in 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

5.3.9. Yield in a Zip Loop

>>> 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'

5.3.10. Example

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]

5.3.11. Assignments

Code 5.13. Solution
"""
* Assignment: Generator Function Passwd
* Complexity: medium
* Lines of code: 10 lines
* Time: 5 min

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
    >>> 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
watney:x:1000:1000:Mark Watney:/home/watney:/bin/bash
lewis:x:1001:1001:Melissa Lewis:/home/lewis:/bin/bash
martinez:x:1002:1002:Rick Martinez:/home/martinez:/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):
    ...