4. Generatory i list comprehension

4.1. Lazy evaluation

import datetime


print(datetime.datetime.now())

range(0, 9_999_999)

print(datetime.datetime.now())

for i in range(0, 9_999_999):
    pow(i, 10)

print(datetime.datetime.now())

4.2. List comprehension

  • wykonywane natychmiast
[x*x for x in range(0, 30) if x % 2]

4.3. Generator expressions

  • lazy evaluation
(x*x for x in range(0, 30) if x % 2)

4.4. List comprehension vs. Generator expressions

print('List Comprahension')

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

print(nieparzyste_list_comp)
# [1, 9, 25, 49, 81, 121, 169, 225, 289, 361, 441, 529, 625, 729, 841]

print(nieparzyste_list_comp)
# [1, 9, 25, 49, 81, 121, 169, 225, 289, 361, 441, 529, 625, 729, 841]
print('\nGenerator Expression')

# tu nastąpi tylko przypisanie do generatora
nieparzyste_generator = (x * x for x in range(0, 30) if x % 2)

print(list(nieparzyste_generator))  # tu dopiero nastąpi wywołanie
# [1, 9, 25, 49, 81, 121, 169, 225, 289, 361, 441, 529, 625, 729, 841]

# tu już generator jest pusty
print(list(nieparzyste_generator))
# []

4.5. Operator yield

osoby_w_klasie = [
    {'username': 'ivan-ivanovic', 'czy_wykladowca': True},
    {'username': 'max-peck', 'czy_wykladowca': False},
    {'username': 'jose-jimenez', 'czy_wykladowca': False},
]


def uczestnicy_kursu_lista():
    uczniowie = []

    for osoba in osoby_w_klasie:
        if not osoba.get('czy_wykladowca'):
            uczen = osoba.get('username')
            uczniowie.append(uczen)

    return uczniowie


for uczestnik in uczestnicy_kursu_lista():
    print('certyfikat dla', uczestnik)
osoby_w_klasie = [
    {'username': 'ivan-ivanovic', 'czy_wykladowca': True},
    {'username': 'max-peck', 'czy_wykladowca': False},
    {'username': 'jose-jimenez', 'czy_wykladowca': False},
]

def uczestnicy_kursu_yield():
    for osoba in osoby_w_klasie:
        if not osoba.get('czy_wykladowca'):
            yield osoba.get('username')


for uczestnik in uczestnicy_kursu_yield():
    print('certyfikat dla', uczestnik)
osoby_w_klasie = [
    {'username': 'ivan-ivanovic', 'czy_wykladowca': True},
    {'username': 'max-peck', 'czy_wykladowca': False},
    {'username': 'jose-jimenez', 'czy_wykladowca': False},
]


def uczestnicy_kursu(osoby):
    def jest_wykladowca(user):
        if user['czy_wykladowca']:
            return True
        else:
            return False

    for osoba in osoby:
        if not osoba['czy_wykladowca']:
            yield {
                'wykladowcy': jest_wykladowca,
                'uczestnicy': [x for x in osoby if not x['czy_wykladowca']],
                'wszystkie_username': [x['username'] for x in osoby]
            }


uczestnicy_kursu = [osoba.get('username') for osoba in osoby_w_klasie if not osoba['czy_wykladowca']]
pprint(uczestnicy_kursu)

4.6. Przykłady

4.6.1. Przykładowe inicjalizacje generatorów

a = [x for x in range(0, 30)]
b = (x for x in range(0, 30))
c = {x for x in range(0, 30)}
d = list(x for x in range(0, 30))
e = tuple(x for x in range(0, 30))
f = set(x for x in range(0, 30))

print(x for x in range(0, 30))

4.6.2. Zamiana klucz wartość oraz generowanie dict i set

>>> osoba = {'username': 'Ivan Ivanovic', 'czy_wykladowca': True}

>>> out = {wartosc: klucz for klucz, wartosc in osoba.items()}

>>> print(out)
{'wykladowca1': 'Ivan Ivanovic', True: 'czy_wykladowca'}

>>> type(out)
<class 'dict'>

>>> out = {wartosc for klucz, wartosc in osoba.items()}

>>> print(out)
{'Ivan Ivanovic', True}

>>> type(out)
<class 'set'>

4.6.3. Filtrowanie wyników na liście dictów

ADDRESS_BOOK = [
    {'imie': 'Ivan',
    'nazwisko': 'Ivanovic',
    'ulica': 'Wochod',
    'miasto': 'Bajkonur',
    'kod_pocztowy': '101503',
    'wojewodztwo': 'Kyzyłordyńskie',
    'panstwo': 'Kazachstan'},

    {'imie': 'José',
    'nazwisko': 'Jiménez',
    'ulica': '2101 E NASA Pkwy',
    'miasto': 'Huston',
    'kod_pocztowy': '77058',
    'wojewodztwo': 'Texas',
    'panstwo': 'USA'},
]

osoby = [{'imie': x['imie'], 'nazwisko': x['nazwisko']} for x in ADDRESS_BOOK]
print(osoby)

4.6.4. Zaawansowane wykorzystanie List Comprehension

def parzyste_f1(x):
    if x % 2 == 0:
        return True
    else:
        return False

def parzyste_f2(x):
    return x % 2 == 0

parzyste1 = [float(x) for x in range(0, 30) if x % 2 == 0]
parzyste2 = [float(x) for x in range(0, 30) if parzyste_f1(x)]
parzyste3 = []

for x in range(0, 30):
    if x % 2 == 0:
        parzyste3.append(float(x))

def parzyste_f3():
    parzyste = []

    for x in range(0, 30):
        if x % 2 == 0:
            parzyste.append(float(x))

    return parzyste

a = range(0, 30)

4.6.5. Zaawansowane wykorzystanie Generator Expressions

liczby = (x for x in range(0, 30))
parzyste1 = (x for x in range(0, 30) if x % 2 == 0)

MAX = 30
parzyste1 = (x for x in range(0, MAX) if x % 2 == 0)

p = lambda a: (x for x in range(0, a) if x % 2 == 0)

def xxx(a):
    return (x for x in range(0, a) if x % 2 == 0)

p(2)
xxx(2)

parzyste2 = (x for x in range(0, a) if x % 2 == 0)
DATA = [
    {'last_name': 'Jiménez'},
    {'first_name': 'Max', 'last_name': 'Peck'},
    {'first_name': 'Ivan'},
    {'first_name': 'Max', 'last_name': 'Peck', 'born': 1961},
    {'first_name': 'Max', 'last_name': 'Peck', 'born': 1961, 'first_step': 1969},
]


# Wykorzystując listę
fieldnames = []

for record in DATA:
    for key in record.keys():
        if key not in fieldnames:
            fieldnames.append(key)

print('list():', fieldnames)


# set(), podejście 1
# Wykorzystując zbiór, który deduplikuje za nas
fieldnames = set()

for record in DATA:
    for key in record.keys():
        fieldnames.add(key)

print('set(), podejście 1:', fieldnames)


# set(), podejście 2
# Wykorzystując zbiór, który deduplikuje za nas
fieldnames = set()

for key in [record.keys() for record in DATA]:
    fieldnames.update(key)

print('set(), podejście 2:', fieldnames)


# set(), podejście 3
# Wykorzystując zbiór, który deduplikuje za nas
fieldnames = set()

for record in DATA:
    fieldnames.update(record.keys())

print('set(), podejście 3:', fieldnames)

4.6.6. Nested list comprahension

DATA = [
     {'last_name': 'Jiménez'},
     {'first_name': 'Max', 'last_name': 'Peck'},
     {'first_name': 'Ivan'},
     {'first_name': 'Max', 'last_name': 'Peck', 'born': 1961},
     {'first_name': 'Max', 'last_name': 'Peck', 'first_step': 1969},
 ]

 # set(), podejście 4
 # Wykorzystując zbiór, który deduplikuje za nas
 fieldnames = set()
 fieldnames.update(key for record in DATA for key in record.keys())
 print('set(), podejście 4:', fieldnames)

4.6.7. Uwaga, czytelność kodu ma znaczenie

Code Listing 4.1. Clean Code in generator
osoba = {'username': 'Ivan Ivanovic', 'czy_wykladowca': True}


def asd(x):
    return x.replace('a', 'b')


out = {
    wartosc: asd()
    for klucz, wartosc in osoba.items()
    if klucz == 'username'
}

4.7. Zadania kontrolne

4.7.1. Generatory vs. Przetwarzanie Listy

Napisz program, który wczyta plik Code Listing 4.2., a następnie:

  • przefiltruje linie, tak aby nie zawierały komentarzy (zaczynające się od #) oraz pustych linii
  • przefiltruje linie, aby wyciągnąć konta systemowe - użytkowników, którzy mają UID (trzecie pole) mniejsze niż 1000
  • zwróci listę loginów takich użytkowników
  • Zaimplementuj rozwiązanie wykorzystując zwykłą funkcję.
  • Zaimplementuj rozwiązanie wykorzystując generator i słówko kluczowe yield.
  • Porównaj wyniki jednego i drugiego rozwiązania przez użycie sys.getsizeof()
Code Listing 4.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
jimenex:x:1001:1001:Jose Jimenez:/home/jimenez:/bin/bash
ivanovic:x:1002:1002:Ivan Ivanovic:/home/ivanovic:/bin/bash