Cvičení č. 12 rozšířené o poznámky ze cvičení a řešení některých příkladů (ZS 2024/25)¶
V minulém díle jste viděli...¶
Základní kontejnery jsou:
str
(string neboli textový řetězec) – posloupnost znakůlist
(seznam) – modifikovatelná (mutable) posloupnost libovolných objektůtuple
(n-tice) – nemodifikovatelná (immutable) posloupnost libovolných objektůset
(množina) – datová struktura obsahující objekty, pro které není určené jejich vzájemné pořadídict
(slovník) – datová struktura, která představuje zobrazení z množiny klíčů (keys) na libovolné hodnoty (values)
Základní operace s kontejnery jsou:
- testování:
var in container
– výraz s hodnotouTrue
/False
- iterování:
for var in container
Seznam zapisujeme pomocí hranatých závorek a n-tice pomocí kulatých závorek:
# posloupnosti:
my_list = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
my_tuple = (10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
my_string = "abcde"
print(my_list, my_tuple, my_string, sep ="\n" )
# další kontejnery:
my_set = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 10, 10}
my_dict = {10: "a", 20: "a", 30: "a", 40: "a", 50: "a", 60: "a", 70: "a", 80: "a", 90: "a", 100: "a"}
print(my_set)
print(my_dict)
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100] (10, 20, 30, 40, 50, 60, 70, 80, 90, 100) abcde {100, 70, 40, 10, 80, 50, 20, 90, 60, 30} {10: 'a', 20: 'a', 30: 'a', 40: 'a', 50: 'a', 60: 'a', 70: 'a', 80: 'a', 90: 'a', 100: 'a'}
Pro přístup k jednotlivým prvkům posloupnosti používáme operátor []
a celočíselnou hodnotu:
print(my_list[0])
print(my_list[-1])
print(my_list[2:-2:2])
print(my_string[::-1])
len(my_list)
for x in my_string:
print(x, end = " ")
my_list[0] = 457
print(my_list)
#my_tuple[0] = 45 # chyba
45 100 [30, 50, 70] edcba a b c d e [457, 20, 30, 40, 50, 60, 70, 80, 90, 100]
a = list(my_string)
print(a)
b = dict([(1,3), (2,4)])
print(b)
['a', 'b', 'c', 'd', 'e'] {1: 3, 2: 4}
Množiny¶
Množina (set
) je datová struktura, která se od posloupností liší v tom, že prvky množiny nemají určené pořadí a daná hodnota se v množině může vyskytovat nejvýše jednou.
Kvůli kontrole unikátnosti je na objekty, které vkládáme do množiny, kladen dodatečný požadavek – musí být hashovatelné (hashable).
Většina nemodifikovatelných (immutable) objektů v Pythonu jsou hashovatelné, modifikovatelné kontejnery hashovatelné nejsou.
Podobně jako v případě seznamu a n-tice má Python dva množinové typy: set
(modifikovatelná/mutable množina) a frozenset
(nemodifikovatelná/immutable množina).
Množina se typicky používá v algoritmech, kde je potřeba zajistit unikátnost prvků.
Pokud algoritmus formulujeme matematicky, odpovídá set
přímo matematickému pojmu množina.
V kódu jazyka Pythonu zapisujeme množiny pomocí složených závorek {
a }
, podobně jako v matematice.
Objekty typu set
mají dvě hlavní metody: add
pro přidání prvku a remove
pro odebrání prvku.
Kompletní přehled dostupných metod je možné najít v dokumentaci.
Příklad: určení počtu unikátních znaků v textovém řetězci
mnozina = {1, 3, 2, 4, 5, 3, 2}
print(mnozina)
# množina ze seznamu ... odstraní duplicity, nezachová pořadí
seznam = [1,3,2,5,2,2,2]
mnozina = set(seznam)
print(mnozina)
mnozina = set("abababa")
print(mnozina)
# prázdná množina:
mnozina = set()
print(mnozina)
mnozina = {1, 3, 2, 4, 5, 3, 2}
print(mnozina)
# podmínka: je prvek v množině?
if 1 in mnozina:
print("je tam")
# for-cyklus přes prvky množiny:
for x in mnozina:
print(x, end = " ")
# počet prvků
print(len(mnozina))
{1, 2, 3, 4, 5} {1, 2, 3, 5} {'a', 'b'} set() {1, 2, 3, 4, 5} je tam 1 2 3 4 5 5
mnozina = {1,2,(1,2)}
print(mnozina)
mnozina = {1,2,[1,2]} # chyba
print(mnozina)
mnozina = {1,2,{1,2}} # chyba
print(mnozina)
{1, 2, (1, 2)}
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[11], line 3 1 mnozina = {1,2,(1,2)} 2 print(mnozina) ----> 3 mnozina = {1,2,[1,2]} # chyba 4 print(mnozina) 5 mnozina = {1,2,{1,2}} # chyba TypeError: unhashable type: 'list'
mnozina = {1, 3, 2, 4, 5, 3, 2}
print(mnozina)
# přidání prvku do množiny
mnozina.add(6)
mnozina.add(6)
print(mnozina)
# smazání prvku z množiny
mnozina.remove(3)
#mnozina.remove(3)
print(mnozina)
{1, 2, 3, 4, 5} {1, 2, 3, 4, 5, 6} {1, 2, 4, 5, 6}
# vstupní data
string = "Hello, world!"
# vytvoření prázdné množiny
unique_chars = set()
# výpočet
for c in string:
# vložení znaku do množiny (pokud tam ještě není)
unique_chars.add(c)
# výpis
print("unikátní znaky:", unique_chars)
print("počet unikátních znaků je", len(unique_chars))
unikátní znaky: {',', 'r', 'o', ' ', 'w', '!', 'e', 'l', 'd', 'H'} počet unikátních znaků je 10
# vstupní data
string = "Hello, world!"
# vytvoření prázdné množiny
unique_chars = set(string)
# výpis
print("unikátní znaky:", unique_chars)
print("počet unikátních znaků je", len(unique_chars))
unikátní znaky: {',', 'r', 'o', ' ', 'w', '!', 'e', 'l', 'd', 'H'} počet unikátních znaků je 10
S objekty set
a frozenset
je také možné provádět množinové operace v matematickém smyslu.
V následujícím přehledu předpokládáme, že proměnné a
a b
označují množiny:
a <= b
– kontrola podmnožiny ($a \subseteq b$)a < b
– kontrola podmnožiny ($a \subset b$)a >= b
– kontrola nadmnožiny ($a \supseteq b$)a > b
– kontrola nadmnožiny ($a \supset b$)a | b | ...
– sjednocení množin ($a \cup b \cup \ldots$)a & b & ...
– průnik množin ($a \cap b \cap \ldots$)a - b - ...
– množinový rozdíl (vrátí prvky, které jsou va
, ale neb
ani v...
)a ^ b
– symetrický množinový rozdíl (vrátí prvky, které jsou buď va
nebob
, ale ne v obou současně)
a = {1,3,4}
b = {3, 6}
print(a == a, a != b, a <= b, b > a, a | b, a & b, a - b, a ^ b)
True True False False {1, 3, 4, 6} {3} {1, 4} {1, 4, 6}
Příklad¶
Napište funkci sudoku_line_check(line)
, která zkontroluje, zda předaný seznam celých čísel reprezentuje správný řádek vyplněného Sudoku, tj. obsahuje pouze čísla 1 až 9 a každé z nich právě jedenkrát.
def sudoku_line_check(line):
if len(line) != 9:
return False
mnozina = set(line)
if mnozina != {1, 2, 3, 4, 5, 6, 7, 8, 9}:
return False
return True
print(sudoku_line_check([1, 2, 8, 9, 3, 5, 6, 7, 4]))
assert sudoku_line_check([1, 2, 8, 9, 3, 5, 6, 7, 4]) == True
assert sudoku_line_check([1, 2, 8, 9, 3, 5, 7, 4]) == False
assert sudoku_line_check([1, 1, 2, 8, 9, 3, 5, 7, 4]) == False
assert sudoku_line_check([1, 6, 1, 2, 8, 9, 3, 5, 7, 4]) == False
assert sudoku_line_check([0, 1, 2, 3, 4, 5, 6, 7, 8]) == False
True
def sudoku_line_check(line):
sudoku_set = set()
for x in line:
if isinstance(x, int) and (1 <= x <= 9) and (x not in sudoku_set):
sudoku_set.add(x)
else:
return False
if len(sudoku_set) != 9:
return False
return True
assert sudoku_line_check([1, 2, 8, 9, 3, 5, 6, 7, 4]) == True
assert sudoku_line_check([1, 2, 8, 9, 3, 5, 7, 4]) == False
assert sudoku_line_check([1, 1, 2, 8, 9, 3, 5, 7, 4]) == False
assert sudoku_line_check([1, 6, 1, 2, 8, 9, 3, 5, 7, 4]) == False
assert sudoku_line_check([0, 1, 2, 3, 4, 5, 6, 7, 8]) == False
Slovníky¶
Slovník (dict
) je modifikovatelná (mutable) datová struktura, která představuje zobrazení z množiny klíčů (keys) na libovolné hodnoty (values).
Klíče slovníku musí být unikátní, ale hodnoty se mohou opakovat.
Klíče tedy podléhají stejným požadavkům, jako množina – musí být hashovatelné.
Pro zápis slovníků v kódu se také používají složené závorky, ale se syntaktickým rozdílem kvůli mapování klíčů na hodnoty. Každý prvek slovníku je dvojice hodnot klíč-hodnota, které jsou oddělené dvojtečkou:
slovník = {1: "a", 2: "bc", 7: "hello"}
print(slovník)
{1: 'a', 2: 'bc', 7: 'hello'}
zamestnanec = {"jmeno": "Petr", "prijmeni": "Novak", "mzda": 50000, "rodne prijmeni": "Novak"}
print(zamestnanec)
sachovnice = {("a", 1): "bílá věž", ("d", 2): "černý kůn"}
print(sachovnice[("a", 1)])
print(sachovnice["a", 1])
sachovnice["c", 2] = "černý král"
print(sachovnice)
{'jmeno': 'Petr', 'prijmeni': 'Novak', 'mzda': 50000, 'rodne prijmeni': 'Novak'} bílá věž bílá věž {('a', 1): 'bílá věž', ('d', 2): 'černý kůn', ('c', 2): 'černý král'}
zamestnanec = {"jmeno": "Petr", "prijmeni": "Novak", "mzda": 50000, "rodne prijmeni": "Novak"}
print(zamestnanec)
# získání hodnoty pro daný klíč:
x = zamestnanec["jmeno"]
print(x)
# změna hodnoty ve slovníku:
zamestnanec["mzda"] += 20000
print(zamestnanec)
# změna hodnoty ve slovníku nebo přidání nového prvku do slovníku:
zamestnanec["zarazeni"] = "ucetni"
print(zamestnanec)
# smazání prvku ve slovníku
del zamestnanec["mzda"]
print(zamestnanec)
{'jmeno': 'Petr', 'prijmeni': 'Novak', 'mzda': 50000, 'rodne prijmeni': 'Novak'} Petr {'jmeno': 'Petr', 'prijmeni': 'Novak', 'mzda': 70000, 'rodne prijmeni': 'Novak'} {'jmeno': 'Petr', 'prijmeni': 'Novak', 'mzda': 70000, 'rodne prijmeni': 'Novak', 'zarazeni': 'ucetni'} {'jmeno': 'Petr', 'prijmeni': 'Novak', 'rodne prijmeni': 'Novak', 'zarazeni': 'ucetni'}
# získání hodnoty pro daný klíč:
y = zamestnanec.get("jmeno")
print(y)
x = zamestnanec["jmeno"]
print(x)
y = zamestnanec.get("jmeno1") # vrátí None
print(y)
x = zamestnanec["jmeno1"] # zhavaruje (KeyError)
print(x)
Petr Petr None
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) Cell In[17], line 9 7 y = zamestnanec.get("jmeno1") # vrátí None 8 print(y) ----> 9 x = zamestnanec["jmeno1"] # zhavaruje (KeyError) 10 print(x) KeyError: 'jmeno1'
Důležitá otázka: Je proměnná var
v následujícím kódu prázdná množina nebo prázdný slovník? Jak můžeme získat to druhé?
var = {}
print(type(var))
var = set()
print(type(var))
var = dict()
print(type(var))
var = list()
print(type(var))
var = ()
print(type(var))
s = {}
s[0] = 5
print(s)
<class 'dict'> <class 'set'> <class 'dict'> <class 'list'> <class 'tuple'> {0: 5}
Jak bylo zmíněno v dřívější sekci, dvě základní operace (testování a iterování) jsou dostupné pro všechny kontejnery, tedy i pro slovníky. Je však důležité si uvědomit, že v obou případech se operace provádí na množině klíčů:
slovník = {1: "a", 2: "b", 3: "c"}
print(1 in slovník) # slovník obsahuje klíč s hodnotou 1
print("a" in slovník) # slovník neobsahuje klíč s hodnotou "a"
True False
Iterování přes slovník pomocí for
cyklu vrací implicitně pouze klíče:
# výpis klíčů:
for i in slovník:
print(i)
1 2 3
# výpis klíčů:
for i in slovník.keys():
print(i)
slovník.keys()
1 2 3
dict_keys([1, 2, 3])
# výpis hodnot:
for i in slovník.values():
print(i)
list(slovník.values())
a b c
['a', 'b', 'c']
for i in slovník.items():
print(i)
list(slovník.items())
(1, 'a') (2, 'b') (3, 'c')
[(1, 'a'), (2, 'b'), (3, 'c')]
for key, value in slovník.items():
print(key, value)
list(slovník.items())
1 a 2 b 3 c
[(1, 'a'), (2, 'b'), (3, 'c')]
Pokud chceme v kódu explicitně vyznačit iterování přes množinu klíčů, můžeme místo celého slovníku jako zdroj dat použít přístupovou metodu keys
: for i in slovník.keys():
a chování bude ekvivalentní předchozímu příkladu.
Také se hodí lépe pojmenovat iterační proměnnou (např. key
místo i
).
Pokud chceme ze slovníku získat hodnotu pro příslušný klíč, můžeme použít indexovací operátor []
:
for key in slovník.keys():
value = slovník[key] # získání hodnoty pro daný klíč
print(key, value)
1 a 2 b 3 c
Použití klíče a odpovídající hodnoty ve for
cyklu je velmi časté a příkaz pro přístup k hodnotě odpovídající danému klíči by se zbytečně často opakoval.
Proto mají slovníky v Pythonu přístupovou metodu items
, která vrací dvojice (key, value)
.
V příkazu for
se často používá rozbalení do dvojice proměnných, což nám umožní předchozí příklad o jeden řádek zkrátit:
for key, value in slovník.items():
print(key, value)
1 a 2 b 3 c
Pokud ve for
cyklu chceme zpracovat jen hodnoty ze slovníku a klíče by byly zbytečné, můžeme použít přístupovou metodu values
.
V případě, že slovník obsahuje stejnou hodnotu pro několik různých klíčů, bude se zde opakovat (hodnoty slovníku netvoří množinu).
for value in slovník.values():
print(value)
a b c
Další operace se slovníky¶
Rozhraní pro práci se slovníky je odlišné od posloupností.
Kompletní přehled je možné najít v dokumentaci, zde uvedeme jen nejdůležitější operace.
Proměnná d
v tomto přehledu představuje nějaký slovník, key
označuje proměnnou s významem klíče a value
představuje libovolnou hodnotu.
len(d)
– počet prvků (klíčů) ve slovníkud[key]
– přístup k hodnotě odpovídající danému klíči (musí ve slovníku existovat, jinak je to chyba)d[key] = value
– zápis hodnoty pro daný klíč do slovníku (buď vložení nové hodnoty nebo přepsání existující hodnoty)del d[key]
– odstranění klíče (a odpovídající hodnoty) ze slovníkud.get(key)
– vrátí hodnotu odpovídající danému klíči, pokud ve slovníku existuje, jinak vrátíNone
d.get(key, default)
– vrátí odpovídající danému klíči, pokud ve slovníku existuje, jinak vrátídefault
print(slovník)
print(slovník.get(2))
print(slovník.get(25))
{1: 'a', 2: 'b', 3: 'c'} b None
Příklad¶
Napište funkci, která spočítá počet dětí ($<18$ let), dospělých (18-64 let) a seniorů ($\ge65$ let) v zadaném seznamu:
people = [
{"name": "Bob", "age": 72},
{"name": "Ali", "age": 18},
{"name": "Tim", "age": 65},
{"name": "Tom", "age": 50},
{"name": "Joe", "age": 40},
{"name": "Eve", "age": 30},
{"name": "Tom", "age": 25},
{"name": "Kia", "age": 5},
{"name": "Pam", "age": 7},
{"name": "May", "age": 17},
{"name": "Liz", "age": 29},
{"name": "Dom", "age": 68},
{"name": "Sam", "age": 36},
]
def count_people(d):
pocet_deti = 0
pocet_dospelych = 0
pocet_senioru = 0
for person in d:
if person["age"]< 18:
pocet_deti += 1
elif person["age"] >= 64:
pocet_senioru += 1
else:
pocet_dospelych += 1
return pocet_deti, pocet_dospelych, pocet_senioru
count_people(people) # funkce vrátí trojici čísel nebo slovník se třemi kategoriemi DVE FUNKCE
def count_people(d):
pocet_deti = 0
pocet_dospelych = 0
pocet_senioru = 0
for person in d:
if person["age"]< 18:
pocet_deti += 1
elif person["age"] >= 64:
pocet_senioru += 1
else:
pocet_dospelych += 1
return {"pocet deti": pocet_deti, "pocet dospelych": pocet_dospelych, "pocet senioru": pocet_senioru}
count_people(people) # funkce vrátí trojici čísel nebo slovník se třemi kategoriemi DVE FUNKCE
def count_people(d):
slovnik = {"pocet deti": 0, "pocet dospelych": 0, "pocet senioru": 0}
for person in d:
if person["age"]< 18:
slovnik["pocet deti"] += 1
elif person["age"] >= 64:
slovnik["pocet senioru"] += 1
else:
slovnik["pocet dospelych"] += 1
return slovnik
count_people(people) # funkce vrátí trojici čísel nebo slovník se třemi kategoriemi DVE FUNKCE
{'pocet deti': 3, 'pocet dospelych': 7, 'pocet senioru': 3}
Důležité poznámky a příklady¶
Kontejnery jako argumenty funkcí¶
Kontejnery lze předávat jako argumenty funkcím, stejně jako všechny ostatní objekty.
Jediná vlastnost jazyka Python, na kterou je nutné dát pozor, souvisí s definicí parametru s výchozí hodnotou, ve které by se nikdy neměly vyskytovat modifikovatelné kontejnery (např. param=[]
nebo param={}
).
Problematické chování demonstrujeme na následujícím příkladu:
def demo(a=[]):
a.append(5)
print(a)
demo([1,2,3])
demo([1,3])
demo()
demo()
demo()
[1, 2, 3, 5] [1, 3, 5] [5] [5, 5] [5, 5, 5]
def demo(a=[]):
a.append(5)
print(a)
return a
b = demo()
c = demo()
d = demo()
tu = b, c, d
b += (6,)
tu
[5] [5, 5] [5, 5, 5]
([5, 5, 5, 6], [5, 5, 5, 6], [5, 5, 5, 6])
Problém spočívá v tom, že příkaz def
definující funkci demo
se provede jednou, přičemž vznikne právě jeden defaultní objekt (prázdný seznam []
) pro parametr a
.
Poté se funkce demo
volá třikrát bez parametru, tj. se stejným defaultním objektem.
Dodejme, že v případě nemodifikovatelných kontejnerů (např. tuple
nebo str
) tento problém není možné pozorovat, protože jediný defaultní objekt pro daný parametr není možné modifikovat.
Jak spravit předchozí příklad?
Běžně se používá tento přístup: v definici funkce použijeme objekt None
pro odlišení, jestli byl parametr specifikován nebo ne, a potřebný modifikovatelný kontejner vytvoříme uvnitř funkce:
def demo2(a=None):
# kontrola parametru a vytvoření kontejneru
if a is None:
a = []
# zbytek funkce
a.append(5)
print(a)
demo2()
demo2()
demo2()
[5] [5] [5]
Mutable vs. immutable objekty¶
Kontejnery v Pythonu se podle svých vlastností dělí na:
- mutable (modifikovatelné) – např.
list
,set
,dict
- immutable (nemodifikovatelné) – např.
str
,tuple
,frozenset
Modifikovatelné typy mají metody, které umožňují modifikovat daný objekt (např. přidávat nebo odebírat prvky v kontejneru). Naopak nemodifikovatelné typy nemají žádné metody, které by modifikovaly daný objekt – jediný způsob, jak dosáhnout modifikace, je tedy vytvoření nového objektu.
Identita vs hodnota objektů¶
Pro objekty nemodifikovatelných typů je důležitá jen jejich hodnota. Pro objekty modifikovatelných typů je navíc důležitá jejich identita, neboli umístění v paměti počítače. Identity a hodnoty objektů můžeme porovnávat:
- operátory
is
ais not
porovnávají identitu dvou objektů (např. dvě proměnné jsou identické, pokud se odkazují na stejný objekt v paměti počítače) - operátory
==
a!=
porovnávají hodnoty dvou objektů (identické objekty mají vždy stejnou hodnotu, ale dva neidentické objekty mohou a nemusí mít stejnou hodnotu)
Přiřazovací operátor¶
Pro pochopení důsledků modifikovatelné vlastnosti kontejnerů je důležité si uvědomit, jak funguje přiřazovací operátor.
Každá proměnná se skládá z názvu (identifikátoru) v kódu programu a z odkazu na objekt, který je uložený v paměti počítače a který nese samotná data objektu.
Přiřazovací operátor =
v Pythonu vždy mění jen odkaz pro danou proměnnou a nikdy nevytváří kopie objektu na jiném místě v paměti počítače.
Demonstrační příklady:
# DOSLI JSME SEM, PRED TENTO PRIKLAD
# přiřazení číselných hodnot
a = 1
b = a
print(a is b) # `a` a `b` jsou identické
# "modifikace" číselné hodnoty - všechny číselné typy jsou **immutable**
b += 1 # vytvoří nový objekt - ekvivalentní `b = b + 1`
print("a =", a) # vypíše 1
print("b =", b) # vypíše 2
print(a is b) # `a` a `b` nejsou identické
# porovnání operátorů `is` a `==`
a = 1.0
b = 2 / 2
print("a =", a) # vypíše 1.0
print("b =", b) # vypíše 1.0
print("is:", a is b) # `a` a `b` nejsou identické (ale mají stejnou hodnotu)
print("==", a == b)
# porovnání operátorů `is` a `==`
a = 1 + 1
b = 2
print("a =", a) # vypíše 1.0
print("b =", b) # vypíše 1.0
print("is:", a is b) # `a` a `b` nejsou identické (ale mají stejnou hodnotu)
print("==", a == b)
True a = 1 b = 2 False a = 1.0 b = 1.0 is: False == True a = 2 b = 2 is: True == True
# přiřazení mutable objektů
a = [0, 1]
b = a
print(a is b) # `a` a `b` jsou identické
# modifikace kontejneru
b.append(2)
b[0] = 10
print("a =", a) # vypíše [0, 1, 2]
print("b =", b) # vypíše [0, 1, 2]
print(a is b) # `a` a `b` jsou stále identické!
True a = [10, 1, 2] b = [10, 1, 2] True
# přiřazení immutable objektů
a = (0, 1)
b = a
print(a is b) # `a` a `b` jsou identické
# modifikace kontejneru
b += (2,)
a += (2,)
print("a =", a) # vypíše [0, 1, 2]
print("b =", b) # vypíše [0, 1, 2]
print(a is b) # `a` a `b` jsou stále identické!
True a = (0, 1, 2) b = (0, 1, 2) False
Předchozí příklad používá seznam (list
) pro ukázku, ale stejné chování platí všechny mutable objekty, včetně množin a slovníků (set
a dict
).
Naopak pro immutable kontejnery podobný případ nastat nemůže – objekt nelze modifikovat přímo, pro změnu bychom museli vytvořit nový objekt a použít přiřazovací operátor, což by však rozpojilo vazbu dvou proměnných na jeden objekt.
Předávání argumentů funkci¶
Pro předávání argumentů při volání funkce platí stejné chování jako v případě přiřazovacího operátoru – předává se jen odkaz na objekt a nevytváří se kopie objektu na jiném místě v paměti počítače.
Důsledek: modifikace objektu uvnitř funkce se projeví i navenek! (Záleží na situaci, jestli je tento efekt žádoucí nebo nežádoucí...)
def add_to_dict(d, key, value):
if key in d:
print("key", key, "already exists")
else:
d[key] = value
print("added key", key)
people_with_age = {
"Alice": 20,
"Bob": 21,
}
add_to_dict(people_with_age, "John", 22)
print(people_with_age)
add_to_dict(people_with_age, "John", 26)
print(people_with_age)
added key John {'Alice': 20, 'Bob': 21, 'John': 22} key John already exists {'Alice': 20, 'Bob': 21, 'John': 22}
Vytváření kopií mutable kontejnerů¶
Jelikož přiřazovací operátor (např. a = b
) pouze nastaví odkaz na stejný objekt, pro vytvoření skutečné kopie kontejneru jako list
, set
a dict
je nutné postupovat opatrně.
Můžeme např. vytvořit prázdný kontejner a postupně do něj přidat všechny prvky z původního kontejneru:
a = [0, 1]
b = a.copy()
b = a[:]
b = list(a)
b.append(2)
print(a) # prints [0, 1]
print(b) # prints [0, 1, 2]
[0, 1] [0, 1, 2]
a = {0, 1}
b = set(a)
b.add(2)
print(a) # prints [0, 1]
print(b) # prints [0, 1, 2]
{0, 1} {0, 1, 2}
a = {0:"a", 1:"b"}
b = dict(a)
b = a.copy()
b["n"] ="a"
print(a) # prints [0, 1]
print(b) # prints [0, 1, 2]
{0: 'a', 1: 'b'} {0: 'a', 1: 'b', 'n': 'a'}
def copy_list(source):
""" Creates a new list containing all elements of the source.
Does **not** modify the parameter.
"""
copy = []
for element in source:
copy.append(element)
return copy
a = [0, 1]
b = copy_list(a)
b.append(2)
print(a) # prints [0, 1]
print(b) # prints [0, 1, 2]
[0, 1] [0, 1, 2]
Tip:
Výraz b = a[:]
také vytváří kopii. Jde o slicing s defaultní hodnotou pro počátek i konec rozsahu, tj. od začátku po konec posloupnosti.
Příklad: naprogramujte analogické funkce pro kopírování množiny a slovníku.
def copy_set(source):
copy = set() # {}
for x in source:
copy.add(x)
return copy
def copy_dict(source):
copy = {}
for key in source:
copy[key] = source[key]
return copy
def copy_dict(source):
copy = {}
for key, value in source.items():
copy[key] = value
return copy
s1 = {0, 1}
s2 = copy_set(s1)
s1.add(2)
assert s1 == {0, 1, 2}
assert s2 == {0, 1}
d1 = {"a": 0, "b": 1}
d2 = copy_dict(d1)
d1["c"] = 2
assert d1 == {"a": 0, "b": 1, "c": 2}
assert d2 == {"a": 0, "b": 1}
Příklad: naprogramujte obecnou funkci copy
, která vytvoří kopii kontejneru správným způsobem podle toho, o jaký typ kontejneru se jedná.
Typ objektu je možné ověřit buď pomocí funkce isinstance
(if isinstance(obj, list):
atd.) nebo pomocí funkce type
(if type(obj) == list
).
s = {3, 6}
print(isinstance(s, list))
isinstance(s, set)
False
True
def copy(source):
if isinstance(source, list):
copy = []
for element in source:
copy.append(element)
return copy
elif isinstance(source, set):
copy = set()
for key in source:
copy.add(key)
return copy
elif isinstance(source, dict):
copy = {}
for key, value in source.items():
copy[key] = value
return copy
else:
# pokud nevíme, jak vytvořit kopii, vrátíme alespoň odkazu
return source
a = [0, 1]
b = copy(a)
a.append(2)
assert b == [0, 1]
s1 = {0, 1}
s2 = copy(s1)
s1.add(2)
assert s2 == {0, 1}
d1 = {"a": 0, "b": 1}
d2 = copy(d1)
d1["c"] = 2
assert d2 == {"a": 0, "b": 1}
Vytváření hluboké kopie¶
Funkce z předchozí sekce vytváří tzv. mělké kopie – nový objekt vznikne jen pro kontejner nejvyšší úrovně a pro prvky tohoto kontejneru se přiřadí jen odkazy:
s1 = [[0, 1], [2, 3]]
#s2 = copy(s1)
s2 = s1[:]
s2.append([4]) # modifikuje jen s2
s2[0][0]=10
s2[0].append(42) # modifikuje prvek s2 i s1!
print(s1)
print(s2)
print(s1[0] is s2[0])
[[10, 1, 42], [2, 3]] [[10, 1, 42], [2, 3], [4]] True
Pro vytvoření tzv. hluboké kopie je potřeba postupovat rekurzivně a pro každý mutable objekt, na který narazíme, vytvořit kopii:
def deep_copy(source):
""" Vytváří hlubokou kopii zdrojového kontejneru. Nemění parametr `source`.
"""
if isinstance(source, list):
# nejprve vytvoříme prázdný kontejner
copy = []
# poté projdeme všechny prvky
for element in source:
# vložíme hlubokou kopii
#copy.append(element)
copy.append(deep_copy(element))
# vrátíme výsledek
return copy
else:
return source
s1 = [[0, [1, 2]], [2, 3]]
s2 = deep_copy(s1)
s2.append([4]) # modifikuje jen s2
s2[0][1][0]=10
s2[0].append(42) # modifikuje prvek s2 i s1!
print(s1)
print(s2)
print(s1[0] is s2[0])
[[0, [1, 2]], [2, 3]] [[0, [10, 2], 42], [2, 3], [4]] False
def deep_copy(source):
""" Vytváří hlubokou kopii zdrojového kontejneru. Nemění parametr `source`.
"""
if isinstance(source, list):
# nejprve vytvoříme prázdný kontejner
copy = []
# poté projdeme všechny prvky
for element in source:
# vložíme hlubokou kopii
copy.append(deep_copy(element))
# vrátíme výsledek
return copy
elif isinstance(source, set):
copy = set()
for element in source:
# vložíme hlubokou kopii
copy.add(deep_copy(element))
return copy
elif isinstance(source, dict):
copy = {}
for key in source:
# vložíme hlubokou kopii
copy[key] = deep_copy(source[key])
return copy
else:
# pokud source není mutable kontejner, vrátíme odkaz
return source
def modify(source):
""" Pomocná funkce pro testování. **Mění parametr `source`.**
"""
# nejprve přidáme nový prvek do kontejneru
if isinstance(source, list):
source.append(len(source))
elif isinstance(source, set):
for i in range(len(source) + 1):
if i not in source:
source.add(i)
break
elif isinstance(source, dict):
for i in range(len(source.keys()) + 1):
if i not in source:
source[i] = i
break
# poté modifikujeme všechny prvky
if isinstance(source, list) or isinstance(source, set):
for element in source:
modify(element)
elif isinstance(source, dict):
for key, value in source.items():
modify(value)
a = [
[0, 1],
{
"a": {1, 2},
"b": [],
},
{0, 42},
]
b = deep_copy(a)
assert a == b
modify(a)
print(a)
print(b)
assert b == [
[0, 1],
{
"a": {1, 2},
"b": [],
},
{0, 42},
]
[[0, 1, 2], {'a': {0, 1, 2}, 'b': [0], 0: 0}, {0, 1, 42}, 3] [[0, 1], {'a': {1, 2}, 'b': []}, {0, 42}]
Další zajímavé vlastnosti¶
Funkce s obecným počtem parametrů¶
Syntaxe jazyka Python umožňuje definovat funkce s obecným počtem pozičních a pojmenovaných parametrů/argumentů. Obecně může definice funkce vypadat takto:
help(print)
Help on built-in function print in module builtins: print(*args, sep=' ', end='\n', file=None, flush=False) Prints the values to a stream, or to sys.stdout by default. sep string inserted between values, default a space. end string appended after the last value, default a newline. file a file-like object (stream); defaults to the current sys.stdout. flush whether to forcibly flush the stream.
def f(a, b, c, *args, k1, k2, **kwargs):
print(a, b, c, k1, k2)
print("args:", args)
print("kwargs:", kwargs)
print()
Význam jednotlivých parametrů je následující:
a
,b
,c
jsou poziční parametry- parametr označený hvězdičkou, tedy
args
, je tuple obsahující všechny další poziční argumenty k1
,k2
jsou definovány až za*args
, takže je lze specifikovat jen jako pojmenované argumenty- parametr označený dvěma hvězdičkami, tedy
kwargs
, je slovník obsahující všechny další pojmenované argumenty (keyword arguments)
Názvy parametry args
a kwargs
nejsou vynucené, mohli bychom použít libovolné jiné názvy.
Pro přehlednost kódu je však dobré držet se zaběhnuté konvence.
Funkci f
můžeme zavolat např. takto:
f(0, 1, 2, k1=10, k2=20)
f(0, 1, 2, 3, k1=10, k2=20, k3=30)
f(0, 1, 2, 3, 4, 5, 6, k1=10, k2=20, hello="world", aaa=56 )
0 1 2 10 20 args: () kwargs: {} 0 1 2 10 20 args: (3,) kwargs: {'k3': 30} 0 1 2 10 20 args: (3, 4, 5, 6) kwargs: {'hello': 'world', 'aaa': 56}
def f(*args):
for x in args:
print(x, end = " ")
print("")
f(1, 3, 5)
f(2)
f(*[4, "a"])
1 3 5 2 4 a
def f(**kwargs):
for x, value in kwargs.items():
print(x, value)
print("")
f(a=1, b=3, c=5)
f(a=2)
d = {"a": 1, "b": 2}
f(d=d)
f(**d)
a 1 b 3 c 5 a 2 d {'a': 1, 'b': 2} a 1 b 2
def f(*args):
print(len(args))
f(1, 3, 5)
f(2)
3 1
Rozbalovací hvězdička a dvojhvězdička¶
V předchozí sekci jsme viděli, že operátory *
a **
označují v definici funkce obecné parametry pro poziční a pojmenované argumenty.
Co když ale máme v kódu kontejnery, např. seznam nebo slovník, které chceme předat takové funkci?
def print_sequence(*args):
for item in args:
print(item)
print_sequence(1, 3, 2, 3)
s = [0, 1, 2]
print_sequence(s, 6, 7)
1 3 2 3 [0, 1, 2] 6 7
def print_sequence(*args):
for item in args:
print(item)
s = [0, 1, 2]
t = {2, 3}
u = [0, 1, *s, *t, 2]
print(u)
#print_sequence(*s, *t)
d = {"a": 1, "b": 2}
[0, 1, 0, 1, 2, 2, 3, 2]
V předchozím příkladu máme seznam s
, který předáváme funkci print_sequence
.
Jak vypadá v tomto případě args
?
Je to tuple délky 1, kde první prvek je seznam s
(ověřte to pomocí nástroje pytutor).
Toto použití je naprosto funkční, ale možná neodpovídá tomu, co programátor zamýšlel.
Jak můžeme docílit toho, aby args
odpovídal přímo seznamu s
, tj. aby cyklus uvnitř funkce iteroval přes hodnoty seznamu s
?
K tomu nám pomůže tzv. rozbalovací hvězdička:
print_sequence(*s)
0 1 2
Pokud v kódu použijeme operátor *
před názvem nějaké posloupnosti, způsobí rozbalení obsažených prvků na daném místě.
V předchozím příkladu je print_sequence(s)
ekvivalentní print_sequence([0, 1, 2])
, ale print_sequence(*s)
je ekvivalentní print_sequence(0, 1, 2)
.
Rozbalování posloupností není svázáno jen s voláním funkcí. Je to obecný prvek jazyka Python, který lze použít všude tam, kde lze syntakticky použít seznam prvků:
s1 = [10, 20, *s, 30]
print(s1)
[10, 20, 3, 6, 30]
d = {"a": "ahoj", "b": "čau"}
u = {"c": 15, **d, "d": 14}
print(u)
{'c': 15, 'a': 'ahoj', 'b': 'čau', 'd': 14}
Podobným způsobem funguje rozbalování slovníku pomocí **
– vyzkoušejte sami.
Povinně poziční a povinně pojmenované parametry/argumenty¶
Jazyk Python umožňuje při definici funkce označit, které parametry/argumenty jsou povinně poziční a pojmenované:
def f(a, b, c):
– všechny parametry lze použít buď jako poziční, nebo jako pojmenovanédef f(a, b, *, c)
– parametrc
je povinně pojmenovaný (nelze použít jako poziční, např.f(1, 2, 3)
)def f(a, /, b, c)
– parametra
je povinně poziční (nelze použít jako pojmenovaný, např.f(a=1, b=2, c=3)
)def f(a, /, b, *, c):
– parametra
je povinně poziční a parametrc
je povinně pojmenovaný
Vyzkoušejte všechny varianty definice funkce a volání s pozičními/pojmenovanými parametry/argumenty:
def f(*, a, b, c):
print("a =", a)
print("b =", b)
print("c =", c)
#f(1, 2, 3)
#f(1, b=2, c=3)
#f(1, 2, c=3)
f(a=1, b=2, c=3)
a = 1 b = 2 c = 3