9. Itertools a funkce vracející iterátory

Lambda výraz

Slouží k vytváření krátkých anonymních funkcí (funkce bez identifikátoru - názvu). Tělo funkce je syntakticky omezeno na jeden výraz a mohou být použity všude, kde je vyžadována funkce. Sémanticky se jedná o syntaktický cukr pro klasickou funkci.

1
2
3
4
5
6
7
8
9
def apply_operation(operation, a, b):
    return operation(a, b)

def my_sum(a, b):
    return a + b

assert apply_operation(my_sum, 2, 3) == 5

assert apply_operation(lambda a, b: a + b, 2, 3) == 5
1
2
3
4
5
6
7
8
# anonymní funkce pro součet dvou čísel
lambda a, b: a + b

# Out[1]: <function __main__.<lambda>(a, b)>

(lambda a, b: a + b)(1, 2)

# Out[2]: 3

Hezkým příkladem je použití v parametru key u funkce sorted.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# seznam obsahující čísla
sorted([4, 2, 3, 1, 4])

# seznam obsahující n-tice
sorted([(1, "Lukáš Novák", 10),
        (4, "Albert Billa", 1),
        (0, "Pepa Los", 30)])
# Out[0]: [(0, 'Pepa Los', 30), (1, 'Lukáš Novák', 10), (4, 'Albert Billa', 1)]

# seřazeno dle posledního prvku
sorted([(1, "Lukáš Novák", 10), 
        (4, "Albert Billa", 1), 
        (0, "Pepa Los", 30)], key=lambda x: x[2])
# Out[1]: [(4, 'Albert Billa', 1), (1, 'Lukáš Novák', 10), (0, 'Pepa Los', 30)]

Funkce any a all

Vrací True v případě že je některý prvek/jsou všechny prvky iterable vyhodnoceny na pravdu. Pokud je iterable prázdná, vrací False.

1
2
3
any([[], [1, 2, 3], [1]])

all([[], [1, 2, 3], [1]])

Funkce map

Vrací iterátor (pozor nikoli sekvenci), který aplikuje funkci na každý prvek předaného iterable. Tyto výsledky jsou pomoci yield vráceny.

1
map(lambda x: x ** 2, [1, 2, 3, 4])

Funkce map může přijímat libovolný počet iterable, v takovém případě však musí aplikovaná funkce přijímat stejný počet argumentů jako je počet iterable.

1
2
3
4
import operator

# operator.mul přijímá 2 argumenty
map(operator.mul, [1, 2, 3, 4], [2, 3, 4, 5])

Funkce filter

Vrací iterátor (pozor nikoli sekvenci) vytvořený z prvků iterable pro které se funkce vyhodnotí na pravdu. Pokud je funkce rovna None jsou z iterable tímto způsobem odstraněny všechny prvky vyhodnocené na nepravdu.

1
2
3
filter(None, [[], [1, 2, 3], [1]])

filter(lambda x: x % 3, range(20))

Funkce filter(function, iterable) je ekvivalentní generátorovému výrazu:

1
2
3
4
5
# pokud je function rovno None
(item for item in iterable if function(item))

# jinak
(item for item in iterable if item)

Modul itertools

Modul implementující sadu užitečných iterátorů. Tyto iterátory preferujeme nad vlastní implementací z důvodu rychlosti a paměťové úspornosti.

itertools.starmap

Vytvoří iterátor, který vyhodnocuje předanou funkci s argumenty získanými z iterable. Narozdíl od klasického map přijímá jeden iterable, ten však může obsahovat n-tice (v případě funkce přijímající n argumentů). Sémanticky podobné použítí * při volání funkce.

1
2
3
4
5
import itertools
import operator

# operator.mul přijímá 2 argumenty
itertools.starmap(operator.mul, [[1, 2], [2, 3], [3, 4], [4, 5]])

itertools.filterfalse

Pracuje podobně jako filter, výsledný iterátor však produkuje prvky pro které je funkce nepravda.

1
2
3
4
5
import itertools

itertools.filterfalse(None, [[], [1, 2, 3], [1]])

itertools.filterfalse(lambda x: x % 3, range(20))

Ostatní iterátory

Celkový výčet iterátorů dostupných v balíčku itertools je dostupný zde.

List comprehension vs map/filter

Funkci map/filter je vhodné nahrazovat list comprehension, můžeme dosáhnout vyššího výkonu. Jedná se však o konstantní overhead volání funkce.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import timeit

# 10
print(timeit.timeit("list(map(lambda x: x * x, range(10)))", number=10000))
# 0.016546586997719714

print(timeit.timeit("[x * x for x in range(10)]", number=10000))
# 0.008891337001841748

# 10000
print(timeit.timeit("list(map(lambda x: x * x, range(10000)))", number=10000))
# 7.573531197998818

print(timeit.timeit("[x * x for x in range(10000)]", number=10000))
# 5.103068967000581

# 100000
print(timeit.timeit("list(map(lambda x: x * x, range(100000)))", number=10000))
# 96.5402136729972

print(timeit.timeit("[x * x for x in range(100000)]", number=10000))
# 63.06499578600051

Úkoly

Nevíte si rady? Přečtěte si “Jak pracovat s Github Classroom?”.