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 ofiter(obj)
Iterator has
obj.__next__()
method which enable use ofnext(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¶
"""
* Assignment: Generator Function Passwd
* Complexity: medium
* Lines of code: 10 lines
* Time: 8 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):
...