15. Generators and Comprehensions

15.1. Lazy evaluation

  • Code do not execute instantly
  • Sometimes code is not executed at all!

15.1.1. Declaring generators

# This will not execute code!
range(0, 9_999_999)
range(0, 9_999_999)
range(0, 9_999_999)
# This will only create generator expression, but not execute it!
numbers = range(0, 9_999_999)
print(numbers)
# range(0, 9999999)

15.1.2. Getting values from generator

numbers = range(0, 9_999_999)
list(range)  # Generator will execute here (not very efficient)
# Generator will execute once at a time, for every loop iteration
for i in range(0, 9_999_999):
    print(i)

15.2. Generator expressions vs. Comprehensions

15.2.1. Comprehensions

  • Executes instantly
a = [x for x in range(0, 10)]
b = {x for x in range(0, 10)}
c = {x: x for x in range(0, 10)}
d = list(x for x in range(0, 10))
e = set(x for x in range(0, 10))
f = dict(x: x for x in range(0, 10))
g = tuple(x for x in range(0, 10))

15.2.2. Generator Expressions

  • Lazy evaluation
i = (x*x for x in range(0, 30) if x % 2)

15.2.3. What is the difference?

# tutaj nastąpi wykonanie i przypisanie
numbers = [x**2 for x in range(0, 30) if x % 2 == 0]

print(numbers)
# [0, 4, 16, 36, 64, 100, 144, 196, 256, 324, 400, 484, 576, 676, 784]

print(numbers)
# [0, 4, 16, 36, 64, 100, 144, 196, 256, 324, 400, 484, 576, 676, 784]
# tu nastąpi tylko przypisanie do generatora
numbers = (x**2 for x in range(0, 30) if x % 2 == 0)

print(numbers)
# <generator object <genexpr> at 0x11af5a570>

print(list(numbers))
# [0, 4, 16, 36, 64, 100, 144, 196, 256, 324, 400, 484, 576, 676, 784]

print(list(numbers))
# []

15.2.4. Which one is better?

  • Comprehensions - Using values more than one
  • Generators - Using value one (for example in the loop iterator)

15.2.5. Nested Comprahensions

DATA = [
     {'last_name': 'Jiménez'},
     {'first_name': 'Mark', 'last_name': 'Watney'},
     {'first_name': 'Иван'},
     {'first_name': 'Matt', 'last_name': 'Kowalski', 'born': 1961},
     {'first_name': 'Melissa', 'last_name': 'Lewis', 'first_step': 1969},
 ]

 fieldnames = set()
 fieldnames.update(key for record in DATA for key in record.keys())

15.2.6. Readability counts

Code Listing 15.1. Clean Code in generator
DATA = {'username': 'Иван Иванович', 'agency': 'Roscosmos'}


def asd(x):
    return x.replace('Иван', 'Ivan')


out = {
    value: asd(value)
    for key, value in DATA.items()
    if key == 'username'
}

print(out)
# {'Иван Иванович': 'Ivan Ivanович'}


out = ['CCCP' if key == 'Roscosmos' else 'USA' for key, value in DATA.items() if key == 'agency']
print(out)
# ['USA']

15.3. Operator yield

# ('Sepal length', 'Sepal width', 'Petal length', 'Petal width', 'Species'),
DATA = [
    (5.1, 3.5, 1.4, 0.2, 'setosa'),
    (4.9, 3.0, 1.4, 0.2, 'setosa'),
    (5.4, 3.9, 1.7, 0.4, 'setosa'),
    (4.6, 3.4, 1.4, 0.3, 'setosa'),
    (7.0, 3.2, 4.7, 1.4, 'versicolor'),
    (6.4, 3.2, 4.5, 1.5, 'versicolor'),
    (5.7, 2.8, 4.5, 1.3, 'versicolor'),
    (5.7, 2.8, 4.1, 1.3, 'versicolor'),
    (6.3, 3.3, 6.0, 2.5, 'virginica'),
    (5.8, 2.7, 5.1, 1.9, 'virginica'),
    (4.9, 2.5, 4.5, 1.7, 'virginica'),
]
def get_species(species):
    output = []

    for record in DATA:
        if record[4] == species:
            output.append(record)

    return output


data = get_species('setosa')

print(data)
# [(5.1, 3.5, 1.4, 0.2, 'setosa'),
#  (4.9, 3.0, 1.4, 0.2, 'setosa'),
#  (5.4, 3.9, 1.7, 0.4, 'setosa'),
#  (4.6, 3.4, 1.4, 0.3, 'setosa')]


for row in data:
    print(row)
# (5.1, 3.5, 1.4, 0.2, 'setosa')
# (4.9, 3.0, 1.4, 0.2, 'setosa')
# (5.4, 3.9, 1.7, 0.4, 'setosa')
# (4.6, 3.4, 1.4, 0.3, 'setosa')
def get_species(species):
    for record in DATA:
        if record[4] == species:
            yield record

data = get_species('setosa')

print(data)
# <generator object get_species at 0x11af257c8>


for row in data:
    print(row)
# (5.1, 3.5, 1.4, 0.2, 'setosa')
# (4.9, 3.0, 1.4, 0.2, 'setosa')
# (5.4, 3.9, 1.7, 0.4, 'setosa')
# (4.6, 3.4, 1.4, 0.3, 'setosa')

15.4. The whys and wherefores

15.4.1. Filtering list items

DATA = [
    (5.1, 3.5, 1.4, 0.2, 'setosa'),
    (4.9, 3.0, 1.4, 0.2, 'setosa'),
    (5.4, 3.9, 1.7, 0.4, 'setosa'),
    (4.6, 3.4, 1.4, 0.3, 'setosa'),
    (7.0, 3.2, 4.7, 1.4, 'versicolor'),
    (6.4, 3.2, 4.5, 1.5, 'versicolor'),
    (5.7, 2.8, 4.5, 1.3, 'versicolor'),
    (5.7, 2.8, 4.1, 1.3, 'versicolor'),
    (6.3, 3.3, 6.0, 2.5, 'virginica'),
    (5.8, 2.7, 5.1, 1.9, 'virginica'),
    (4.9, 2.5, 4.5, 1.7, 'virginica'),
]

setosa = [x for x in DATA if x[4] == 'setosa']
print(setosa)

15.4.2. Filtering dict items

DATA = [
    {'first_name': 'Иван', 'last_name': 'Иванович', 'agency': 'Roscosmos'},
    {'first_name': 'Jose', 'last_name': 'Jimenez', 'agency': 'NASA'},
    {'first_name': 'Melissa', 'last_name': 'Lewis', 'agency': 'NASA'},
    {'first_name': 'Alex', 'last_name': 'Vogel', 'agency': 'ESA'},
    {'first_name': 'Mark', 'last_name': 'Watney', 'agency': 'NASA'},
]

nasa_astronauts = [(astronaut['first_name'], astronaut['last_name']) for astronaut in DATA if astronaut['agency'] == 'NASA']
# [
#   ('Jose', 'Jimenez'),
#   ('Melissa', 'Lewis'),
#   ('Mark', 'Watney')
# ]

nasa_astronauts = [(x['first_name'], x['last_name']) for x in DATA if x['agency'] == 'NASA']
# [
#   ('Jose', 'Jimenez'),
#   ('Melissa', 'Lewis'),
#   ('Mark', 'Watney')
# ]

15.4.3. Reversing dict keys with values

data = {'first_name': 'Иван', 'last_name': 'Иванович'}

{v: k for k, v in data.items()}
# dict {'Иван': 'first_name', 'Иванович': 'last_name'}

15.4.4. Applying functions

[float(x) for x in range(0, 10) if x % 2 == 0]
# [0.0, 2.0, 4.0, 6.0, 8.0]
def is_even(x):
    if x % 2 == 0:
        return True
    else:
        return False

[float(x) for x in range(0, 10) if is_even(x)]
# [0.0, 2.0, 4.0, 6.0, 8.0]

15.5. Assignments

15.5.1. Generators vs. Comprehensions - iris

  1. Skopiuj dane z https://raw.githubusercontent.com/scikit-learn/scikit-learn/master/sklearn/datasets/data/iris.csv do pliku “iris.csv”
  2. Zaczytaj dane pomijając nagłówek
  3. Napisz funkcję która zwraca wszystkie pomiary dla danego gatunku
  4. Gatunek będzie podawany jako str do funkcji
  5. Zaimplementuj rozwiązanie wykorzystując zwykłą funkcję
  6. Zaimplementuj rozwiązanie wykorzystując generator i słówko kluczowe yield
About:
  • Filename: generator_iris.py
  • Lines of code to write: 40 lines
  • Estimated time of completion: 20 min
The whys and wherefores:
 
  • Wykorzystanie generatorów
  • Odbieranie danych z lazy evaluation
  • Porównanie wielkości struktur danych
  • Parsowanie pliku
  • Filtrowanie treści w locie

15.5.2. Generators vs. Comprehensions - passwd

  1. Napisz program, który wczyta plik Code Listing 15.2.
  2. Przefiltruj linie, tak aby nie zawierały komentarzy (zaczynające się od #) oraz pustych linii
  3. Przefiltruj linie, aby wyciągnąć konta systemowe - użytkowników, którzy mają UID (trzecie pole) mniejsze niż 1000
  4. Zwróć listę loginów użytkowników systemowych
  5. Zaimplementuj rozwiązanie wykorzystując zwykłą funkcję
  6. Zaimplementuj rozwiązanie wykorzystując generator i słówko kluczowe yield
  7. Porównaj wyniki jednego i drugiego rozwiązania przez użycie sys.getsizeof()
About:
  • Filename: generator_passwd.py
  • Lines of code to write: 40 lines
  • Estimated time of completion: 20 min
The whys and wherefores:
 
  • Wykorzystanie generatorów
  • Odbieranie danych z lazy evaluation
  • Porównanie wielkości struktur danych
  • Parsowanie pliku
  • Filtrowanie treści w locie
Code Listing 15.2. /etc/passwd sample file
##
# User Database
#   - User name
#   - Encrypted password
#   - User ID number (UID)
#   - User's group ID number (GID)
#   - Full name of the user (GECOS)
#   - User home directory
#   - Login shell
##

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
peck:x:1000:1000:Max Peck:/home/peck:/bin/bash
jimenez:x:1001:1001:José Jiménez:/home/jimenez:/bin/bash
ivanovic:x:1002:1002:Ivan Иванович:/home/ivanovic:/bin/bash