# 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: The whys and wherefores: Filename: generator_iris.py Lines of code to write: 40 lines Estimated time of completion: 20 min 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: The whys and wherefores: Filename: generator_passwd.py Lines of code to write: 40 lines Estimated time of completion: 20 min 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