Cvičení č. 12 rozšířené o poznámky ze cvičení a řešení některých příkladů (ZS 2025/26)¶

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:

  1. testování: var in container – výraz s hodnotou True/False
  2. iterování: for var in container

Seznam zapisujeme pomocí hranatých závorek a n-tice pomocí kulatých závorek:

In [1]:
my_list  = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
my_tuple = (10, 20, 30, 40, 50, 60, 70, 80, 90, 100)

Pro přístup k jednotlivým prvkům posloupnosti používáme operátor [] a celočíselnou hodnotu:

In [2]:
print(my_list[0])
print(my_list[-1])
print(my_list[2:-2])
10
100
[30, 40, 50, 60, 70, 80]

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.

In [2]:
# ukázka navíc: 
# každý prvek je v množině pouze jednou, nejasné pořadí prvků při výpisu
mnozina = {10, "a", 1, 2, 7, 15, 1, 1, 7}
print(mnozina)

# množina ze seznamu ... odstraní duplicity, nezachová pořadí
s = [10, "a", 1, 2, 7, 15, 1, 1, 7]
mnozina = set(s)
print(mnozina)

# množina ze stringu
s = "ahoj ahoj!"
mnozina = set(s)
print(mnozina)

# prázdná množina
mnozina = set()
print(mnozina)
{'a', 2, 1, 7, 10, 15}
{'a', 2, 1, 7, 10, 15}
{'a', 'o', 'h', 'j', ' ', '!'}
set()
In [3]:
# podmínka: je prvek v množině?
mnozina = {1, 3, 2, 4, 5, 3, 2}
print(mnozina)

if 1 in mnozina:
    print("je tam")

# for-cyklus přes prvky množiny:
for x in mnozina:
    print(x, end = " ")

# počet prvků
L = len(mnozina)
print("")
print(L)

# indexace a výřezy ... ne (chyba)
mnozina[0]
{1, 2, 3, 4, 5}
je tam
1 2 3 4 5 
5
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[3], line 18
     15 print(L)
     17 # indexace a výřezy ... ne (chyba)
---> 18 mnozina[0]

TypeError: 'set' object is not subscriptable
In [14]:
# ukázka navíc: které prvky mohou být v množině a které ne
# ano: hashable objekty (většina immutable objektů)
mnozina = {1, 2.5, 2+3j, True, "abc", (1, 3)}
print(mnozina)
{'abc', 2.5, 1, (2+3j), (1, 3)}
In [15]:
# ne: mutable objekty (list, set, dict)
mnozina = {(1, 3), [1, 4]} # chyba
print(mnozina)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[15], line 1
----> 1 mnozina = {(1, 3), [1, 4]}
      2 print(mnozina)

TypeError: unhashable type: 'list'
In [16]:
mnozina = {(1, 3), {1, 4}} # chyba
print(mnozina)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[16], line 1
----> 1 mnozina = {(1, 3), {1, 4}}
      2 print(mnozina)

TypeError: unhashable type: 'set'
In [4]:
# ukázka navíc: přidání prvku do množiny
mnozina = {1, 4, 5}

mnozina.add(7)
print(mnozina)

mnozina.add(7) # mohu přidat podruhé
print(mnozina)

# smazání prvku z množiny
mnozina.remove(5)
print(mnozina)
mnozina.remove(5) # nemohu smazat podruhé
print(mnozina)
{1, 4, 5, 7}
{1, 4, 5, 7}
{1, 4, 7}
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[4], line 13
     11 mnozina.remove(5)
     12 print(mnozina)
---> 13 mnozina.remove(5)
     14 print(mnozina)

KeyError: 5

Příklad: určení počtu unikátních znaků v textovém řetězci

In [22]:
# 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: {'e', ' ', 'w', 'l', '!', ',', 'o', 'r', 'H', 'd'}
počet unikátních znaků je 10
In [23]:
# ukázka navíc: to samé stručně
# 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: {'e', ' ', 'w', 'l', '!', ',', 'o', 'r', 'H', 'd'}
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 v a, ale ne b ani v ...)
  • a ^ b – symetrický množinový rozdíl (vrátí prvky, které jsou buď v a nebo b, ale ne v obou současně)
In [31]:
# ukázka
a = {1, 7, 3}
b = {1, 3, 7, 9}
print(a == b, a <= b, a < b, a | b, a & b, b - a, a ^ b)
False True True {1, 3, 7, 9} {1, 3, 7} {9} {9}

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.

In [5]:
def sudoku_line_check(line):
    if len(line) != 9:
        return False
    mnozina = {1, 2, 3, 4, 5, 6, 7, 8, 9}
    if set(line) != mnozina:
        return False
    return True

def sudoku_line_check(line):
    mnozina = set()
    for x in line:
        if isinstance(x, int) and (1 <=  x <= 9) and x not in mnozina:
            mnozina.add(x)
        else:
            return False
    if len(mnozina) != 9:
        return False
    return True

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

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:

In [38]:
slovník = {1: "a", 2: "bc", 7: "hello"}
print(slovník)
{1: 'a', 2: 'bc', 7: 'hello'}
In [15]:
# ukázka navíc: zaměstnanec
zamestnanec = {"jmeno": "Jan", "prijmeni": "Novak", "mzda": 70000, "jmeno": "Jan"}
print(zamestnanec)
{'jmeno': 'Jan', 'prijmeni': 'Novak', 'mzda': 70000}
In [16]:
# ukázka navíc: zaměstnanec a duplicitní klíče
zamestnanec = {"jmeno": "Jan", "prijmeni": "Novak", "mzda": 70000, "jmeno": "Pavel"}
print(zamestnanec)
{'jmeno': 'Pavel', 'prijmeni': 'Novak', 'mzda': 70000}
In [17]:
# ukázka navíc: zaměstnanec a duplicitní hodnoty
zamestnanec = {"jmeno": "Jan", "prijmeni": "Novak", "mzda": 70000, "jmeno2": "Jan"}
print(zamestnanec)
{'jmeno': 'Jan', 'prijmeni': 'Novak', 'mzda': 70000, 'jmeno2': 'Jan'}

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é?

In [13]:
var = dict()
print(var)
print(type(var))

var = {}
print(var)
print(type(var))
{}
<class 'dict'>
{}
<class 'dict'>
In [14]:
var = set()
print(var)
print(type(var))
set()
<class 'set'>
In [12]:
var = list()
print(var)
print(type(var))

var = []
print(var)
print(type(var))
[]
<class 'list'>
[]
<class 'list'>

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íčů:

In [50]:
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
In [53]:
zamestnanec = {"jmeno": "Jan", "prijmeni": "Novak", "mzda": 70000, "jmeno2": "Jan"}
print(zamestnanec)
print("jmeno" in zamestnanec)
print("Jan" in zamestnanec)
{'jmeno': 'Jan', 'prijmeni': 'Novak', 'mzda': 70000, 'jmeno2': 'Jan'}
True
False

Iterování přes slovník pomocí for cyklu vrací implicitně pouze klíče:

In [55]:
for x in slovník:
    print(x)
for x in zamestnanec:
    print(x)
1
2
3
jmeno
prijmeni
mzda
jmeno2
In [60]:
# metody, co vracejí posloupnosti klíčů, hodnot a dvojic (klíč, hodnota)
print(zamestnanec)
print(list(zamestnanec.keys()))
print(list(zamestnanec.values()))
print(list(zamestnanec.items()))
{'jmeno': 'Jan', 'prijmeni': 'Novak', 'mzda': 70000, 'jmeno2': 'Jan'}
['jmeno', 'prijmeni', 'mzda', 'jmeno2']
['Jan', 'Novak', 70000, 'Jan']
[('jmeno', 'Jan'), ('prijmeni', 'Novak'), ('mzda', 70000), ('jmeno2', 'Jan')]
In [68]:
# můžeme přes ně iterovat
for x in zamestnanec:
    print(x, end = " ")
print("")

for x in zamestnanec.keys():
    print(x, end = " ")
print("")

for x in zamestnanec.values():
    print(x, end = " ")
print("")

for x in zamestnanec.items():
    print(x, end = " ")
print("")

for klic, hodnota in zamestnanec.items():
    print(klic, hodnota, end = ", ")
jmeno prijmeni mzda jmeno2 
jmeno prijmeni mzda jmeno2 
Jan Novak 70000 Jan 
('jmeno', 'Jan') ('prijmeni', 'Novak') ('mzda', 70000) ('jmeno2', 'Jan') 
jmeno Jan, prijmeni Novak, mzda 70000, jmeno2 Jan, 

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 []:

In [10]:
for key in slovník.keys():
    value = slovník[key]  # získání hodnoty pro daný klíč
    print(key, value)

# ukázka: převod na list
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:

In [ ]:
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).

In [11]:
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íku
  • d[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íku
  • d.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
In [82]:
# ukázka navíc: zaměstnanec
zamestnanec = {"jmeno": "Jan", "prijmeni": "Novak", "mzda": 70000, "jmeno2": "Jan"}
print(len(zamestnanec))
x = zamestnanec["jmeno"] 
print(x)
x = zamestnanec.get("jmeno3", "nezname") 
print(x)
# získání hodnoty pro daný klíč pomocí [] a get():

# změna hodnoty pro daný klíč
zamestnanec["mzda"] += 5000
zamestnanec["jmeno"] = "Pavel"
print(zamestnanec)

# změna hodnoty ve slovníku nebo přidání nového prvku do slovníku:
zamestnanec["oddeleni"] = "uctarna"
print(zamestnanec)

# smazání prvku ve slovníku
del zamestnanec["jmeno2"]
print(zamestnanec)
4
Jan
nezname
{'jmeno': 'Pavel', 'prijmeni': 'Novak', 'mzda': 75000, 'jmeno2': 'Jan'}
{'jmeno': 'Pavel', 'prijmeni': 'Novak', 'mzda': 75000, 'jmeno2': 'Jan', 'oddeleni': 'uctarna'}
{'jmeno': 'Pavel', 'prijmeni': 'Novak', 'mzda': 75000, 'oddeleni': 'uctarna'}
In [83]:
# ukázka: postupné přidávání prvků do původně prázdného slovníku
d = {}
d["jmeno"] = "Petr"
d["prijmeni"] = "Novak"
print(d)
{'jmeno': 'Petr', 'prijmeni': 'Novak'}
In [20]:
# ukázka navíc: sachovnice
sachovnice = {("a", 1) : "bily kral", ("d", 4) : "cerny strelec"}

x = sachovnice.get(("a", 1))
print(x)
x = sachovnice.get(("a", 3))
print(x)

sachovnice["c", 4] = "cerny kral"
print(sachovnice)
bily kral
None
{('a', 1): 'bily kral', ('d', 4): 'cerny strelec', ('c', 4): 'cerny kral'}

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:

In [30]:
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_people1(d):
    ...

count_people1(people)  # funkce vrátí trojici čísel nebo slovník se třemi kategoriemi
In [22]:
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},
]
print(people[0])
x = people[0]
print(x["name"], x["age"])

for person in people:
    print(person)
    break
{'name': 'Bob', 'age': 72}
Bob 72
{'name': 'Bob', 'age': 72}
In [104]:
def count_people1(d): # funkce vrátí trojici čísel
    child = 0
    adult = 0
    senior = 0
    for person in d:
        if person["age"] < 18:
            child += 1
        elif person["age"] >= 65:
            senior += 1
        else:
            adult += 1
    return child, adult, senior

x, y, z = count_people1(people)  
print(x, y, z)
3 7 3
In [102]:
def count_people2(d): # funkce vrátí slovnik se třemi kategoriemi
    child = 0
    adult = 0
    senior = 0
    for person in d:
        if person["age"] < 18:
            child += 1
        elif person["age"] >= 65:
            senior += 1
        else:
            adult += 1
    return {"pocet deti": child, "pocet dospelych": adult, "pocet senioru": senior}

d = count_people2(people)  
print(d)
print(d["pocet deti"])
{'pocet deti': 3, 'pocet dospelych': 7, 'pocet senioru': 3}
3
In [23]:
def count_people3(d): # funkce vrátí slovnik se třemi kategoriemi
    result = {"pocet deti": 0, "pocet dospelych": 0, "pocet senioru": 0}
    for person in d:
        if person["age"] < 18:
            result["pocet deti"] += 1
        elif person["age"] >= 65:
            result["pocet senioru"] += 1
        else:
            result["pocet dospelych"] += 1
    return result

d = count_people3(people)  
print(d)
print(d["pocet deti"])
{'pocet deti': 3, 'pocet dospelych': 7, 'pocet senioru': 3}
3
In [ ]:
# dalsi funkce nad stejnym seznamem
# vratit seznam jmen 
# vratit seznam duplicitnich jmen
# vratit slovnik tvaru {"Bob": 72, "Ali": 18, ...}

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:

In [28]:
def demo(a=[]):
    a.append(5)
    #print(a)
    return a
a = demo([1, 2, 3])
print(a)
b = demo([7, 7])
print(b)
c = demo()
print(c)
d = demo()
print(d)
e = demo()
print(e)
print(a, b, c, d, e)
[1, 2, 3, 5]
[7, 7, 5]
[5]
[5, 5]
[5, 5, 5]
[1, 2, 3, 5] [7, 7, 5] [5, 5, 5] [5, 5, 5] [5, 5, 5]

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:

In [113]:
def demo2(a=None):
    # kontrola parametru a vytvoření kontejneru
    if a is None:
        a = []

    # zbytek funkce
    a.append(5)
    return a

a = demo2()
b = demo2()
c = demo2()
print(a, b, c)
[5] [5] [5]

Mutable vs. immutable objekty¶

Kontejnery v Pythonu se podle svých vlastností dělí na:

  1. mutable (modifikovatelné) – např. list, set, dict
  2. 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 a is 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:

In [29]:
# jednodušší ukázka a, b, ==, is ... pro číslo a pro seznam
a = 7
b = a
print("a =", a)  
print("b =", b)  
print(a == b) # True
print(a is b) # True
a = 7
b = 7
True
True
In [115]:
# jednodušší ukázka a, b, ==, is ... pro číslo a pro seznam
a = [1, 2, 3]
b = a
print("a =", a) 
print("b =", b) 
print(a == b) # True
print(a is b) # True
a = [1, 2, 3]
b = [1, 2, 3]
True
True
In [116]:
a = [1, 2, 3]
b = [1, 2, 3]
print("a =", a) 
print("b =", b)  
print(a == b) # True
print(a is b) # False
a = [1, 2, 3]
b = [1, 2, 3]
True
False
In [33]:
# !!! operátor IS se pro immutable objekty chová nevyzpytatelně
a = 7
b = 7
print("a =", a)  
print("b =", b) 
print(a == b) # True
print(a is b) # True

a = 1.0
b = 1.0
print("a =", a)
print("b =", b) 
print(a == b) # True
print(a is b) # False
a = 7
b = 7
True
True
a = 1.0
b = 1.0
True
False
In [35]:
# !!! operátor IS se pro immutable objekty chová nevyzpytatelně
a = 1 + 1
b = 20 // 10
print("a =", a)  
print("b =", b)  
print(a == b) # True
print(a is b) # True

a = 1000 + 1000
b = 20000 // 10
print("a =", a)  
print("b =", b)
print(a == b) # True
print(a is b) # False
a = 2
b = 2
True
True
a = 2000
b = 2000
True
False
In [15]:
# to samé zhuštěně
# 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("is:", a is b)  # `a` a `b` nejsou identické (ale mají stejnou hodnotu)
print("==", a == b)
True
a = 1
b = 2
False
is: False
== True
In [121]:
# přiřazení mutable objektů
a = [0, 1]
b = a
print(a is b)  # `a` a `b` jsou identické

# modifikace kontejneru
b.append(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]
True
In [1]:
# přiřazení immutable objektů ... tuple
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` nejsou identické!
print(a == b)
True
a = (0, 1, 2)
b = (0, 1, 2)
False
True
In [2]:
# přiřazení immutable objektů ... číslo
a = 10
b = a
print(a is b)  # `a` a `b` jsou identické

# změna immutable objektu
b +=  2

print("a =", a)  # vypíše 10
print("b =", b)  # vypíše 12
print(a is b)    # False
print(a == b)    # False
True
a = 10
b = 12
False
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í...)

In [4]:
# podobně pro volání funkce, kde je mutable objekt argumentem
def f(t):
    t.append(1)
    t.sort()
    print("lokální:", t)

s = [1, 7, 2, 4]
f(s)
print("globální:", s)
lokální: [1, 1, 2, 4, 7]
globální: [1, 1, 2, 4, 7]
In [5]:
# globání immutable objekt se uvnitř funkce nezmění
def f(t):
    t += (1, 2)
    print("lokální:", t)

s = (1, 7, 2, 4)
f(s)
print("globální:", s)
lokální: (1, 7, 2, 4, 1, 2)
globální: (1, 7, 2, 4)
In [6]:
# globální immutable objekt se uvnitř funkce nezmění
def f(x):
    x += 3
    print("lokální:", x)

x = 4
f(x)
print("globální:",x)
lokální: 7
globální: 4
In [125]:
# mutable (dict)
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)
added key John
{'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:

In [8]:
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]
In [9]:
# ukázka: seznam, množina, slovník a kopie jednoduseji
# seznam
a = [1, 3]

b1 = copy_list(a)
b2 = list(a)
b3 = a.copy()
b4 = a[ : ]

print(a is b1, a is b2, a is b3, a is b4)
a.append(2)
print(a)  
print(b1, b2, b3, b4)  
False False False False
[1, 3, 2]
[1, 3] [1, 3] [1, 3] [1, 3]
In [10]:
# množina
a = {1, 3}

b1 = set(a)
b2 = a.copy()

print(a is b1, a is b2)
a.add(2)
print(a)  
print(b1, b2) 
False False
{1, 2, 3}
{1, 3} {1, 3}
In [11]:
# slovník
a = {1: "a", 3: "b"}

b1 = dict(a)
b2 = a.copy()

print(a is b1, a is b2)
a[5] = "c"
print(a)  
print(b1, b2) 
False False
{1: 'a', 3: 'b', 5: 'c'}
{1: 'a', 3: 'b'} {1: 'a', 3: 'b'}

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.

In [12]:
#d[key] = value
In [ ]:
def copy_list(source):
    """ Creates a new list containing all elements of the source.
        Does **not** modify the parameter.
    """
    copy = [] # list()
    for element in source:
        copy.append(element)
    return copy

def copy_set(source):
    copy = set()
    for element in source:
        copy.add(element)
    return copy
    
def copy_dict(source):
    copy = {} # dict()
    for key in source:
        copy[key] = source[key]
    return copy

def copy_dict(source):
    copy = {} # dict()
    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).

In [13]:
s = [1, 3]
type(s) == list
Out[13]:
True
In [14]:
# ukázka isinstance
isinstance(s, list), isinstance(s, tuple)
Out[14]:
(True, False)
In [15]:
def copy(source):
    if isinstance(source, list):
        copy = []
        for element in source:
            copy.append(element)
        return copy

    elif isinstance(source, set):
        copy = set()
        for element in source:
            copy.add(element)
        return copy

    elif isinstance(source, dict):
        copy = {} # dict()
        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:

In [16]:
s1 = [[0, 1], [2, 3]]
s2 = copy(s1)
s2.append([4])  # modifikuje jen s2
print(s1, s2)
# plus obrázek na tabuli s odkazy
[[0, 1], [2, 3]] [[0, 1], [2, 3], [4]]
In [17]:
s2[0].append(42)  # modifikuje prvek s2 i s1!
print(s1)
print(s2)
print(s1[0] is s2[0])
[[0, 1, 42], [2, 3]]
[[0, 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:

In [18]:
# nejprve pro seznam seznamů ...
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


def deep_copy(source):
    """ Vytváří hlubokou kopii zdrojového kontejneru. Nemění parametr `source`.
    """
    if isinstance(source, list):
        copy = []
        for element in source:
            copy.append(deep_copy(element))
        return copy 
    else:
        # pokud source není mutable kontejner, vrátíme odkaz
        return source

s1 = [[0, 1], [2, 3]]

s2 = deep_copy(s1)

s2.append([4])  # modifikuje jen s2
s2[0].append(42)  # modifikuje prvek s2 i s1!
print(s1)
print(s2)
print(s1[0] is s2[0])
[[0, 1], [2, 3]]
[[0, 1, 42], [2, 3], [4]]
False
In [19]:
def deep_copy(source):
    """ Vytváří hlubokou kopii zdrojového kontejneru. Nemění parametr `source`.
    """
    if isinstance(source, list) or isinstance(source, tuple):
        # 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 if isinstance(source, list) else tuple(copy)

    elif isinstance(source, set):
        copy = set()
        for element in source:
            copy.add(deep_copy(element))
        return copy

    elif isinstance(source, dict):
        copy = {}
        for key, val in source.items():
            copy[key] = deep_copy(val)
        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:

In [20]:
# ukázka ... print
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.

In [21]:
def f(a, b, c, *args, k1, k2, **kwargs):
    print("args:", args)
    print("kwargs:", kwargs)
    print(a, b, c, k1, k2)
    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:

In [22]:
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, k1=10, k2=20, hello="world")
args: ()
kwargs: {}
0 1 2 10 20

args: (3,)
kwargs: {'k3': 30}
0 1 2 10 20

args: (3, 4)
kwargs: {'hello': 'world'}
0 1 2 10 20

In [33]:
# ukázka ... jen args
def f(*args):
    print("args:", args)
    print("pocet argumentu:", len(args))
    for x in args:
        print(x, end = " ")
    print("\n")
f()
f(1)
f(2, 3)
args: ()
pocet argumentu: 0


args: (1,)
pocet argumentu: 1
1 

args: (2, 3)
pocet argumentu: 2
2 3 

In [40]:
def f(**kwargs):
    print("kwargs:", kwargs)
    print("pocet argumentu:", len(kwargs))
    for x, y in kwargs.items():
        print(x, y,  end = ", ")
    print("\n")
f()
f(x=1)
f(a=2, b=3)
kwargs: {}
pocet argumentu: 0


kwargs: {'x': 1}
pocet argumentu: 1
x 1, 

kwargs: {'a': 2, 'b': 3}
pocet argumentu: 2
a 2, b 3, 

In [44]:
# ukázka ... oboje
def f(*args, **kwargs):
    print("args:", args)
    print("kwargs:", kwargs)
    print("pocty argumentu:", len(args), len(kwargs))
    for x in args:
        print(x, end = " ")
    print()
    for x, y in kwargs.items():
        print(x, y,  end = ", ")
    print("\n")
f(1)
f(x=1)
f(1, 2, a=2, b = 3)
args: (1,)
kwargs: {}
pocty argumentu: 1 0
1 


args: ()
kwargs: {'x': 1}
pocty argumentu: 0 1

x 1, 

args: (1, 2)
kwargs: {'a': 2, 'b': 3}
pocty argumentu: 2 2
1 2 
a 2, b 3, 

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?

In [50]:
def print_sequence(*args):
    for item in args:
        print(item, end = " ")
    print()

s = [0, 1, 2]
print_sequence(s)
print_sequence(1, 2, 4, "ahoj", [1, 3])
[0, 1, 2] 
1 2 4 ahoj [1, 3] 

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:

In [52]:
print_sequence(*s)
print_sequence(*"ahoj")
print_sequence(1, 2, 4, *"ahoj", *(1, 3))
0 1 2 
a h o j 
1 2 4 a h o j 1 3 

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ů:

In [23]:
ss = [10, 20, *s, 30]
print(ss)
[10, 20, 0, 1, 2, 30]
In [54]:
s = {1, 6}
ss = [10, 20, *s, 30]
print(ss)
[10, 20, 1, 6, 30]

Podobným způsobem funguje rozbalování slovníku pomocí ** – vyzkoušejte sami.

In [56]:
d = {1: "a", 2: "b"}
e = {3: 4, **d, 8: 8}
print(e)
{3: 4, 1: 'a', 2: 'b', 8: 8}

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) – parametr c je povinně pojmenovaný (nelze použít jako poziční, např. f(1, 2, 3))
  • def f(a, /, b, c) – parametr a je povinně poziční (nelze použít jako pojmenovaný, např. f(a=1, b=2, c=3))
  • def f(a, /, b, *, c): – parametr a je povinně poziční a parametr c je povinně pojmenovaný

Vyzkoušejte všechny varianty definice funkce a volání s pozičními/pojmenovanými parametry/argumenty:

In [57]:
# abs
help(abs)
abs(-3)
abs(x = -3)
Help on built-in function abs in module builtins:

abs(x, /)
    Return the absolute value of the argument.

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[57], line 4
      2 help(abs)
      3 abs(-3)
----> 4 abs(x = -3)

TypeError: abs() takes no keyword arguments
In [7]:
def f(a, b, c):
    print("a =", a)
    print("b =", b)
    print("c =", c)

f(1, 2, 3)
f(a=1, b=2, c=3)

# ...
a = 1
b = 2
c = 3
a = 1
b = 2
c = 3
In [58]:
def f(a, /, b, * , c):
    print("a =", a)
    print("b =", b)
    print("c =", c)

f(1, 2, c=3)
f(1, b=2, c=3)
a = 1
b = 2
c = 3
a = 1
b = 2
c = 3