V minulém díle jste viděli…¶
Iterovatelné objekty (iterables) v Pythonu jsou objekty, přes které může být provedena iterace, např. pomocí for
-cyklu. Mezi iterovatelné objekty patří např. kontejnery. Za iteraci na iterovatelném objektu je zodpovědný objekt zvaný iterátor, který postupně poskytuje jednotlivé prvky iterovatelného objektu.
Iterátor pro daný iterovatelný objekt získáme pomocí vestavěné funkce iter(iterable)
, další prvek v iteraci získáme pomocí funkce next(iterator)
. Jakmile iterátor poskytne všechny prvky iterovatelného objektu, vyvolá výjimku StopIteraion
, což iteraci ukončí:
my_list = [1, 5, 4]
my_iterator = iter(my_list)
print(next(my_iterator))
print(next(my_iterator))
print(next(my_iterator))
print(next(my_iterator))
1 5 4
--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) Cell In[1], line 7 5 print(next(my_iterator)) 6 print(next(my_iterator)) ----> 7 print(next(my_iterator)) StopIteration:
V programech s iterátory často nepracujeme přímo, ale skrytě - na iterátorech je založený for
-cyklus:
my_list = [1, 8, 3]
for x in my_list:
... # do something
Kód v předchozí buňce můžeme přepsat tímto způsobem:
my_list = [1, 8, 3]
iterator = iter(my_list)
while True:
try:
x = next(iterator)
... # do something
except StopIteration:
break
Seznámili jsme se s několika užitečnými vestavěnými funkcemi v Pythonu, které vrací iterátory:
Funkce | Krátký popis
-----------------------------------|-----------------------------
reversed(sequence)
| Vrací iterátor, který poskytuje prvky posloupnosti sequence
v opačném pořadí.
map(function, iterable)
| Vrací iterátor přes function(x)
pro x
z iterable
.
filter(function, iterable)
| Vrací iterátor přes ta x
z iterable
, pro která function(x)==True
.
zip(*iterables)
| Vrací iterátor přes n-tice (x, y,...)
pro x
z iterable1
, y
z iterable2
,...
enumerate(iterable,start=0)
| Vrací objekt enumerate
- iterátor přes dvojice (index, x)
pro x
z iterable
.
A s dalšími vestavěnými funkcemi nad iterovatelnými objekty:
Funkce | Krátký popis
-----------------------------------|-----------------------------
sorted(iterable, /, *, key=None, reverse=False
) | Vrací setříděný seznam prvků z iterable
.
all(iterable)
| Vrací True
, pokud každý prvek z iterable
má hodnotu True
, jinak vrací False
.
any(iterable)
| Vrací True
, pokud alespoň jeden prvek z iterable
má hodnotu True
, jinak vrací False
.
sum(iterable,/,start=0)
| Vrací součet prvků z iterable
.
max(iterable,/,key=None)
| Vrací největší prvek z iterable
.
min(iterable,/,key=None)
| Vrací nejmenší prvek z iterable
.
range(stop)
, range(start,stop)
, range(start,stop,step)
| Vrací iterovatelný objekt typu range
, nemodifikovatelnou posloupnost čísel.
Generátory¶
Generátory jsou zvláštním druhem iterátoru, který neiteruje přes existující iterovatelný objekt, ale přes průběžně generované hodnoty. Můžeme ho vytvořit pomocí tzv. generátorové funkce (generator function) nebo pomocí tzv. generátorového výrazu (generator expression).
Generátorová funkce je taková funkce, která hodnoty nevrací (pomocí příkazu return
), ale generuje (pomocí příkazu yield
). Například:
def generate_squares(n):
for x in range(n):
yield x ** 2
Kdykoliv v programu zavoláme generátorovou funkci, funkce vrátí nový generátor - tj. iterátor, který iteruje přes hodnoty generované pomocí yield
.
m = generate_squares(5)
print(m)
print(next(m))
<generator object generate_squares at 0x7f30775e7780> 0
for x in m:
print(x, end = " ")
0 1 4 9 16
Podrobněji:
Generátor si pamatuje aktuální pozici v rámci funkce. Při volání next()
iterátor spustí generátorovou funkci od aktuální pozice. Provádění příkazů přeruší u příkazu yield
a jako návratovou hodnotu funkce next()
poskytne příslušnou hodnotu. Při dalším volání next()
funkce pokračuje dál od daného místa. Například:
def generate_whatever():
x = 1
yield x
x = 2
yield x
yield "the end"
m = generate_whatever()
#print(m)
s = list(m)
print(s)
# PyTutor s forcyklem
for x in generate_whatever():
print(x)
for x in generate_whatever():
print(x)
[1, 2, 'the end'] 1 2 the end 1 2 the end
# opět si zkuste pustit tuto buňku opakovaně:
print(next(m))
--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) Cell In[9], line 2 1 # opět si zkuste pustit tuto buňku opakovaně: ----> 2 print(next(m)) StopIteration:
# generátor můžeme převést na iterovatelný objekt:
s = list(generate_whatever())
print(s)
[1, 2, 'the end']
# další ukázka: generátor hodnot od 0 do n-1:
def my_range(n):
i = 0
while i < n:
yield i
i = i + 1
for x in my_range(21):
print(x, end = " ")
print("my_range")
print("poprvé:")
m = my_range(21)
for x in m:
print(x, end = " ")
print("\npodruhe")
for x in m:
print(x, end = " ")
print()
print("range")
m = range(21)
print("poprvé:")
for x in m:
print(x, end = " ")
print("\npodruhe")
for x in m:
print(x, end = " ")
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 my_range poprvé: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 podruhe range poprvé: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 podruhe 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Poznámka: Generátory šetří časové i paměťové nároky programu: nemusíme uchovávat všechna data v paměti najednou (např. v kontejneru), hodnoty jsou generované jedna po druhé až ve chvíli, kdy jsou potřeba.
Generátorové výrazy a generátorová notace modifikovatelných kontejnerů¶
Generátorové výrazy umožňují vytvářet jednoduché generátory elegantně bez nutnosti vytváření generátorových funkcí. Generátorová notace (comprehension) u modifikovatelných (mutable) kontejnerů (list, set, dict) nám pak výrazně zjednodušuje vytváření a zpracování těchto objektů.
Základní syntaxe je následující:
python:
new_generator = (expression for member in iterable)
new_list = [expression for member in iterable]
new_set = {expression for member in iterable}
new_dict = {key:expression for member in iterable}
Výrazy se liší jen typem použitých závorek. Pokud použijeme kulaté závorky, bude výsledkem generátor, jinak příslušný kontejner. Použitou syntaxi si vysvětlíme na několika následujících příkladech:
Například pro seznam příkaz s = [expression for member in iterable]
můžeme přepsat jako:
python:
s = []
for member in iterable:
s.append(expression)
Obdobně pro množinu s = {expression for member in iterable}
:
python:
s = set()
for member in iterable:
s.add(expression)
Pro slovník d = {key:expression for member in iterable}
:
python:
d = {}
for member in iterable:
d[key] = expression
Ukážeme si to na konkrétních příkladech:
# generátorová notace seznamu, množiny a slovníku:
my_tuple = (1,8,3)
my_list = [x*2 for x in my_tuple]
my_set = {x*2 for x in my_tuple}
my_dict = {x:x*2 for x in my_tuple}
print(my_tuple)
print(my_list)
print(my_set)
print(my_dict)
(1, 8, 3) [2, 16, 6] {16, 2, 6} {1: 2, 8: 16, 3: 6}
my_tuple = (1,8,3)
# chci novou ntici, kde budou prvky vynásobené dvěma:
s = []
for x in my_tuple :
s.append(x*2)
t = tuple(s)
print(t)
# alternativně na jeden řádek:
t = tuple(x*2 for x in my_tuple)
print(t)
(2, 16, 6) (2, 16, 6)
# generátorový výraz:
my_generator = (x*2 for x in my_tuple)
print(my_generator)
for x in my_generator:
print(x, end = " ")
print("")
# generátorový výraz přímo ve for-cyklu:
for x in (x*2 for x in my_tuple):
print(x, end = " ")
# generátor převedu na seznam:
l = list(x*2 for x in my_tuple)
print(l)
# je to samé jako:
l = [x*2 for x in my_tuple]
print(l)
<generator object <genexpr> at 0x7f3065799560> 2 16 6 2 16 6
Výraz my_generator = (x**2 for x in my_tuple)
je zkratkou následujícího kódu:
# generátorová funkce:
def generate_squares(source):
for x in source:
yield x ** 2
my_generator = generate_squares(my_tuple)
Výraz new_list = [expression for member in iterable]
můžeme ekvivalentně zapsat jako:
new_list = list(expression for member in iterable)
. Obdobně pro množinu. Pro slovník bude syntaxe lehce odlišná - argumentem funkce dict
může být generátor dvojic (klíč, hodnota)
:
my_tuple = (1,8,3)
my_list = list(x*2 for x in my_tuple)
my_set = set(x*2 for x in my_tuple)
my_dict = dict((x, x*2) for x in my_tuple)
print(my_tuple)
print(my_list)
print(my_set)
print(my_dict)
print()
my_list = [x*2 for x in my_tuple]
my_set = {x*2 for x in my_tuple}
my_dict = {x:x*2 for x in my_tuple}
print(my_tuple)
print(my_list)
print(my_set)
print(my_dict)
# a pro nemodifikovatelné kontejnery:
my_tuple = tuple(x*2 for x in my_tuple)
print(my_tuple)
(1, 8, 3) [2, 16, 6] {16, 2, 6} {1: 2, 8: 16, 3: 6}
Generové výrazy můžeme rozšířit o podmínky a můžeme pomocí nich kombinovat hodnoty z více zdrojů:
- Filtrování (obdoba funkce
filter
):
python:
(expression for member in iterable if condition)
- Kombinace hodnot z více zdrojů:
python:
(expression for member1 in iterable1 (if condition1)
for member2 in iterable2 (if condition2)
...
for memberN in iterableN (if conditionN))
Příkaz s = [f(x) for x in iterable if condition]
je zkratkou za:
python:
s = []
for x in iterable:
if condition:
s.add(f(x))
Příkaz s = [x+y for x in range(5) for y in range(x)]
je zkratkou za:
python:
s = []
for x in range(5):
for y in range(x):
s.append(x+y)
Opět si to ukážeme na konkrétních příkladech:
# generátorová notace seznamu (list comprehension):
s = [x for x in range(1, 11)]
print(s, end ="\n\n")
# totéž:
s = list(range(1,11))
print(s, end ="\n\n")
t = [2 ** x for x in range(1, 11)]
print(t, end ="\n\n")
# generátorová notace množiny (set comprehension) s podmínkou:
m = {x for x in s if x % 2== 0}
print(m, end ="\n\n")
# generátorová notace seznamu s více zdroji:
trojice =[(i,j,k) for i in [1,2,3] for j in {True, False} for k in "AB"]
print(trojice, end ="\n\n")
# generátorová notace seznamu s využitím funkce zip:
trojice =[(i,j,k) for (i,j,k) in zip([1,2,3], {True, False},"AB")]
print(trojice, end ="\n\n")
# generátorová notace slovníku:
šachovnice ={(i,j) : "" for i in "abcdefgh" for j in range(1,8)}
šachovnice["c",4] = "bílý pěšec"
šachovnice["a",1] = "černý král"
print(šachovnice)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024] {2, 4, 6, 8, 10} [(1, False, 'A'), (1, False, 'B'), (1, True, 'A'), (1, True, 'B'), (2, False, 'A'), (2, False, 'B'), (2, True, 'A'), (2, True, 'B'), (3, False, 'A'), (3, False, 'B'), (3, True, 'A'), (3, True, 'B')] [(1, False, 'A'), (2, True, 'B')] {('a', 1): 'černý král', ('a', 2): '', ('a', 3): '', ('a', 4): '', ('a', 5): '', ('a', 6): '', ('a', 7): '', ('b', 1): '', ('b', 2): '', ('b', 3): '', ('b', 4): '', ('b', 5): '', ('b', 6): '', ('b', 7): '', ('c', 1): '', ('c', 2): '', ('c', 3): '', ('c', 4): 'bílý pěšec', ('c', 5): '', ('c', 6): '', ('c', 7): '', ('d', 1): '', ('d', 2): '', ('d', 3): '', ('d', 4): '', ('d', 5): '', ('d', 6): '', ('d', 7): '', ('e', 1): '', ('e', 2): '', ('e', 3): '', ('e', 4): '', ('e', 5): '', ('e', 6): '', ('e', 7): '', ('f', 1): '', ('f', 2): '', ('f', 3): '', ('f', 4): '', ('f', 5): '', ('f', 6): '', ('f', 7): '', ('g', 1): '', ('g', 2): '', ('g', 3): '', ('g', 4): '', ('g', 5): '', ('g', 6): '', ('g', 7): '', ('h', 1): '', ('h', 2): '', ('h', 3): '', ('h', 4): '', ('h', 5): '', ('h', 6): '', ('h', 7): ''}
Příklady¶
Předně můžete zkusit naprogramovat příklady z minulého cvičení tak, aby místo funkcí map
a filter
používaly generátory.
1a. Generátor dělitelů. Napište generátorovou funkci, která vrátí generátor všech dělitelů zadaného přirozeného čísla n. Čísla budou generována v pořadí od nejmenšího po největší.
def divisors_list(n):
s = []
for i in range(1,n+1):
if n % i == 0:
s.append(i)
return s
def divisors_generator(n):
for i in range(1,n+1):
if n % i == 0:
yield i
n = 24
for divisor in divisors_generator(n):
print(divisor, end=' ')
print("")
print(divisors_generator(n))
print(list(divisors_generator(n)))
assert list(divisors_generator(504)) == [1, 2, 3, 4, 6, 7, 8, 9, 12, 14, 18, 21, 24, 28, 36, 42, 56, 63, 72, 84, 126, 168, 252, 504]
1 2 3 4 6 8 12 24 <generator object divisors_generator at 0x7f76844ddfc0> [1, 2, 3, 4, 6, 8, 12, 24]
1b. Seznam dělitelů. Napište funkci, která vrátí seznam všech dělitelů zadaného přirozeného čísla seřazený od nejmenšího po největší. Úlohu řeště tentokrát pomocí generátorové notace seznamu (list comprehension).
def divisors_list(n):
return [ i for i in range(1,n+1) if n % i == 0 ]
n = 24
for divisor in divisors_list(n):
print(divisor, end=' ')
assert divisors_list(504) == [1, 2, 3, 4, 6, 7, 8, 9, 12, 14, 18, 21, 24, 28, 36, 42, 56, 63, 72, 84, 126, 168, 252, 504]
1 2 3 4 6 8 12 24
1c. Generátor dělitelů podruhé. Šla by předchozí funkce jednoduše upravit tak, aby místo seznamu dělitelů vracela generátor dělitelů?
def divisors_generator(n):
return ( i for i in range(1,n+1) if n % i == 0 )
n = 24
for divisor in divisors_generator(n):
print(divisor, end=' ')
assert list(divisors_generator(504)) == [1, 2, 3, 4, 6, 7, 8, 9, 12, 14, 18, 21, 24, 28, 36, 42, 56, 63, 72, 84, 126, 168, 252, 504]
1 2 3 4 6 8 12 24
2a. Generátor posloupnosti Napište generátorovou funkci, která vrátí generátor prvků následující posloupnosti (pro dané n): 1 1 2 1 2 3 1 2 3 4 1 2 3 4 5 ... 1 2 ... n.
def sequence_generator(n):
for i in range(1,n+1):
for j in range(1,i+1):
yield j
n = 5
generator = sequence_generator(n)
for num in generator:
print(num, end=' ')
assert list(sequence_generator(8)) == [1, 1, 2, 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7, 8]
1 1 2 1 2 3 1 2 3 4 1 2 3 4 5
2b. Generátor posloupnosti podruhé Předchozí úlohu řešte pomocí generátorového výrazu nebo generátorové notace seznamu. Funkce bude vracet buď generátor nebo seznam.
def sequence(n):
return (j for i in range(1,n+1) for j in range(1,i+1))
n = 8
nums = sequence(n)
assert list(nums) == [1, 1, 2, 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7, 8]
for num in nums:
print(num, end=' ')
- Prohoď klíče a hodnoty. Naprogramujte funkci, která na základě slovníku vytvoří nový slovník, kde budou prohozené klíče a hodnoty.
original_dict = {'a': 1, 'b': 2, 'c': 3}
for x in original_dict:
print(x)
for x in original_dict.items():
print(x)
a b c ('a', 1) ('b', 2) ('c', 3)
def swap_keys_and_values(input_dict):
#return {value: key for (key, value) in input_dict.items()}
return {input_dict[key]: key for key in input_dict}
original_dict = {'a': 1, 'b': 2, 'c': 3}
swapped_dict = swap_keys_and_values(original_dict)
print(swapped_dict)
assert swapped_dict == {1: 'a', 2: 'b', 3: 'c'}
('a', 1) ('b', 2) ('c', 3) {1: 'a', 2: 'b', 3: 'c'}
4a. Délka slov. Naprogramujte funkci, která zjistí délku každého slova v textu. Předpokládejme, že slova jsou oddělená pomocí obyčejných mezer (možná několika za sebou). Úlohu řešte tentokrát pomocí generátorového výrazu nebo generátorové notace. Hodit se mohou i metody pro stringy (např. split()
).
def word_lengths(text):
return ( len(x) for x in text.split() )
text = "Toto je příklad textu s několika slovy oddělenými mezerami"
# assert (list(word_lengths(text)) == [4, 2, 7, 5, 1, 8, 5, 10, 8])
for length in word_lengths(text):
print(length, end=' ')
#assert remove_long_words(text,6) == "Toto je textu s slovy"
4 2 7 5 1 8 5 10 8
4b. Jen krátká slova. Naprogramujte funkci, která z textu odstraní všechna moc dlouhá slova, tj. slova, která jsou delší než n
písmen.
Předpokládejme, že slova jsou oddělená pomocí obyčejných mezer. Úlohu řešte s využitím generátorové notace. Hodit se mohou i metody pro stringy (např. join()
a split()
).
def remove_long_words(text, n):
filtered = (x for x in text.split() if len(x) <= n )
return " ".join(filtered)
text = "Toto je příklad textu s několika slovy oddělenými mezerami"
new_text = remove_long_words(text,4)
print(new_text)
assert remove_long_words(text,6) == "Toto je textu s slovy"
Toto je s
4c. Zkrať moc dlouhá slova. Naprogramujte funkci, která z textu zkrátí na n
písmen všechna moc dlouhá slova, tj. slova, která jsou delší než n
písmen.
Předpokládejme, že slova jsou oddělená pomocí obyčejných mezer. Úlohu řešte s využitím generátorové notace. Hodit se mohou i metody pro stringy (např. join()
a split()
).
def shorten_long_words(text, n):
filtered = (x[:n] for x in text.split() )
return " ".join(filtered)
text = "Toto je příklad textu s několika slovy oddělenými mezerami"
new_text = shorten_long_words(text,8)
print(new_text)
assert shorten_long_words(text,6) == "Toto je příkla textu s několi slovy odděle mezera"
Toto je příklad textu s několika slovy oddělený mezerami
- Bláznivé křížení zvířat podruhé. Máme dva seznamy
s
,t
s názvy zvířat. Napište funkcimerge_animals(s, t)
, která vytvoří nový seznam, který bude obsahovat "zkřížené" názvy zvířat tak, že z prvního názvu vezmeme vždy první polovinu a z druhého druhou polovinu. Např. 'hroch' a 'tigr' -> 'hrogr'. Úlohu řešte tentokrát pomocí generátorové notace. Hodit se může i funkcezip()
.
def merge_animals(s, t):
return [a[:len(a)//2] + b[len(b)//2:] for a, b in zip(s, t)]
assert merge_animals(["hroch","potkan"],["žirafa","orangutan","lev"]) == ['hrafa', 'potgutan']
- Frekvenční analýza písmen. Napište funkci
freq_analysis(text)
, která spočítá výskyt jednotlivých písmen (znaků) ve vstupním textu a vrátí výsledek jako slovník s prvkypísmeno: počet výskytů
. Malá a velká písmena nebudeme rozlišovat. Úlohu řešte tentokrát pomocí generátorové notace slovníku. Hodit se mohou i různé metody pro stringy (např.count()
,lower()
,upper()
neboisalpha()
). Písmena a počty jejich výskytů vypište setříděné sestupně podle počtu výskytů.
def freq_analysis(text):
...
d = freq_analysis("All the world is a stage and all the men and women merely players.")
print(d)
print(sorted(d.items()))
# vypište setříděné sestupně podle počtu výskytů:
...
None
- Frekvenční analýza slov. Napište funkci
freq_analysis_words(text)
, která spočítá výskytů jednotlivých slov ve vstupním textu a vrátí výsledek jako slovník s prvkyslovo: počet výskytů
. Malá a velká písmena nebudeme rozlišovat. Úlohu řešte tentokrát pomocí generátorové notace slovníku. Hodit se mohou i různé metody pro stringy (např.count()
,lower()
,upper()
nebosplit()
). Slova a počty výskytů vypište setříděné vzestupně podle abecedy.
def freq_analysis(text):
...
d = freq_analysis("This is a test. This is only a test, not a real situation.")
print(d)
# vypište setříděné vzestupně podle abecedy:
...
- indexy Napište funkci, která vrátí seznam všech indexů, kde se v řetězci
string
vyskytuje znakchar
.
Úlohu řešte tentokrát pomocí generátorové notace seznamu nebo generátorové funkce. Hodit se může i funkce enumerate
.
# generátorová notace:
def find_letter_indexes(string, letter):
...
assert find_letter_indexes("hello world", "l") == [2, 3, 9]
# generátorová funkce:
def generate_letter_indexes(string, letter):
...
assert list(generate_letter_indexes("hello world", "l")) == [2, 3, 9]
8b. indexy Napište funkci, která vrátí seznam všech indexů, na kterých v řetězci string
začíná podstring substring
. Úlohu řešte tentokrát pomocí generátorové notace seznamu nebo generátorové funkce. Hodit se může i funkce enumerate
.
def find_substring_indexes(string, substring):
...
assert find_substring_indexes("ara aaraara arara", "ara") == [0, 5, 8, 12, 14]
# generátorová funkce:
def generate_substring_indexes(string, substring):
...
assert list(generate_letter_indexes("ara aaraara arara", "ara")) == [0, 5, 8, 12, 14]