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 = "nějaký text"
print(my_list, my_tuple, my_string, sep ="\n" )
if 110 not in my_list:
my_list.append(110)
my_list.insert(0, 110)
print(my_list)
if 100 in my_list:
my_list.remove(100)
print(my_list)
for x in my_list:
print(x, end = " ")
print("")
l = len(my_list) # počet prvků
print(l)
t = tuple(my_list)
print(t)
Pro přístup k jednotlivým prvkům posloupnosti používáme operátor []
a celočíselnou hodnotu:
my_list = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
# indexy:
print(my_list[0])
print(my_list[-1])
for i in range(len(my_list)):
my_list[i] *= 2
print(my_list)
# výřezy:
print(my_list[2:-2])
print(my_list[ : :-1])
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.
# seznam:
seznam = [10,2,2,4,"a",8,15,8,(1,1)]
print(seznam)
# množina:
m = {10,2,2,4,"a",8,15,8,(1,1)}
print(m)
# množina ze seznamu ... odstraní duplicity, nezachová pořadí
m = set(seznam)
print(m)
# prázdná množina:
empty_set = set()
print(empty_set)
# test, zda je prvek "b" v množině "m":
if "b" not in m:
# přidání prvku
m.add("b")
m.add("b")
m.add("b")
print(m)
if "a" in m:
# odebrání prvku
m.remove("a")
# m.remove("a") # chyba
print(m)
# počet prvků v množině:
l = len(m)
print(l)
[10, 2, 2, 4, 'a', 8, 15, 8, (1, 1)] {'a', 2, (1, 1), 4, 8, 10, 15} {2, 4, 8, 10, (1, 1), 15, 'a'} set() {2, 4, 8, 10, (1, 1), 15, 'b', 'a'} {2, 4, 8, 10, (1, 1), 15, 'b'} 7
Příklad: určení počtu unikátních znaků v textovém řetězci
# vstupní data
string = "abrakadabra"
# 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: {'k', 'a', 'r', 'd', 'b'} počet unikátních znaků je 5
# vstupní data
string = "abrakadabra"
# vytvoření množiny na základě seznamu ... zkráceně
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: {'k', 'a', 'r', 'd', 'b'} počet unikátních znaků je 5
# vstupní data
seznam = [2,4,3,2,3,4,2,2,2,2]
# vytvoření množiny
unique_chars = set(seznam)
# výpis
print("unikátní prvky:", unique_chars)
print("počet unikátních prvku je", len(unique_chars))
unikátní prvky: {2, 3, 4} počet unikátních prvku je 3
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ě)
# různé množinové operace:
x = {1,4,2}
y = {2,8,7,6}
z = {1,2,3,4,5}
print(x < z, x < y, x | y, x & y, x - y, x ^ y)
True False {1, 2, 4, 6, 7, 8} {2} {1, 4} {1, 4, 6, 7, 8}
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):
sudoku_set = set()
for x in line:
if (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
def sudoku_line_check(line):
if len(line) != 9:
return False
sudoku_set = set(line)
if sudoku_set != {1,2,3,4,5,6,7,8,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", 10: "aghfv"}
print(slovník)
{1: 'a', 2: 'bc', 10: 'aghfv'}
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é?
# prázdný seznam
s = []
print(type(s))
# prázdný slovník
var = {}
print(var,type(var))
var = dict()
print(var, type(var))
<class 'list'>
dict
# ukazky slovníků: ... klíčem může být string:
zamestnanec = {"jméno" : "Jana", "příjmení" : "Nováková" , "plat" : 40000 }
print(zamestnanec)
překlady = {"pes" : "dog", "kočka" : "cat", "myš" : "mouse"}
print(překlady)
# klíčem může být tuple (n-tice):
souradnice = {(1,0,1): True, (1,1,1): False}
print(souradnice)
partie = {("c",1) : "bílý kůň", ("d",4) : "černá věž"}
print(partie)
{'jméno': 'Jana', 'příjmení': 'Nováková', 'plat': 40000} {'pes': 'dog', 'kočka': 'cat', 'myš': 'mouse'} {(1, 0, 1): True, (1, 1, 1): False} {('c', 1): 'bílý kůň', ('d', 4): 'černá věž'}
# ukazky slovníků: ... klíčem může být string:
zamestnanec = {"jméno" : "Jana", "příjmení" : "Nováková" , "plat" : 40000 }
print(zamestnanec)
# získání hodnoty pro daný klíč:
x = zamestnanec["jméno"]
y = zamestnanec["plat"]
print(x, y)
# změna hodnoty ve slovníku nebo přidání nového prvku do slovníku:
zamestnanec ["oddělení"] = "sklad"
zamestnanec ["plat"] = 60000
print(zamestnanec)
partie = {("c",1) : "bílý kůň", ("d",4) : "černá věž"}
print(partie)
# indexace pro klíč typu tuple: kulaté závorky mohu vynechat:
partie[("a", 6)] ="černý král"
partie["b", 3] ="bílý král"
print(partie)
# smazání prvku ve slovníku
del partie[("d",4)]
print(partie)
{'jméno': 'Jana', 'příjmení': 'Nováková', 'plat': 40000} Jana 40000 {'jméno': 'Jana', 'příjmení': 'Nováková', 'plat': 60000, 'oddělení': 'sklad'} {('c', 1): 'bílý kůň', ('d', 4): 'černá věž'} {('c', 1): 'bílý kůň', ('d', 4): 'černá věž', ('a', 6): 'černý král', ('b', 3): 'bílý král'} {('c', 1): 'bílý kůň', ('a', 6): 'černý král', ('b', 3): 'bílý král'}
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(1 in slovník.keys())
print("a" in slovník) # slovník neobsahuje klíč s hodnotou "a"
print("a" in slovník.keys())
print("a" in slovník.values())
slovník.values()
True True False False True
dict_values(['a', 'b', 'c'])
Iterování přes slovník pomocí for
cyklu vrací implicitně pouze klíče:
slovník = {1: "a", 2: "b", 3: "c"}
# výpis klíčů:
for i in slovník:
print(i, end = " ")
print("")
# výpis klíčů:
for i in slovník.keys():
print(i, end = " ")
print("")
# výpis hodnot:
for i in slovník.values():
print(i, end = " ")
print("")
# výpis hodnot
for i in slovník:
print(slovník[i], end = " ")
print("")
# výpis klíčů i hodnot:
for key, value in slovník.items():
print(key, value, end = " , ")
print("")
# výpis klíčů i hodnot:
for key in slovník:
print(key, slovník[key], end = " , ")
1 2 3 1 2 3 a b c a b c 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
slovník.items()
dict_items([(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
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):
count_children = 0
count_adult = 0
count_senior = 0
for person in d:
if person["age"]< 18:
count_children += 1
elif 18 <= person["age"]< 65:
count_adult += 1
else:
count_senior += 1
return (count_children, count_adult, count_senior)
x,y,z = count_people(people)
print(count_people(people)) # funkce vrátí trojici čísel
(3, 7, 3)
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):
count_children = 0
count_adult = 0
count_senior = 0
for person in d:
if person["age"]< 18:
count_children += 1
elif 18 <= person["age"]< 65:
count_adult += 1
else:
count_senior += 1
return {"children": count_children, "adult": count_adult, "senior": count_senior}
d = count_people(people)
print(d["children"])
print(count_people(people)) # funkce vrátí slovník se třemi kategoriemi
3 {'children': 3, 'adult': 7, 'senior': 3}
# Další varianta:
def count_people(d):
ret = {"children" : 0, "adult" : 0, "senior" : 0}
for person in d:
if person["age"]< 18:
ret["children"] += 1
elif 18 <= person["age"]< 65:
ret["adult"] += 1
else:
ret["senior"] += 1
return ret
count_people(people) # funkce vrátí slovník se třemi kategoriemi
{'children': 3, 'adult': 7, 'senior': 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()
demo([1,2,3])
demo()
demo()
[5] [1, 2, 3, 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:
def demo2(a=None):
# kontrola parametru a vytvoření kontejneru
if a is None:
a = []
# zbytek funkce
a.append(5)
print(a)
demo2()
demo2([1,2,3])
demo2()
demo2()
[5] [1, 2, 3, 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:
# 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
# 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
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,
}
print(people_with_age)
add_to_dict(people_with_age, "John", 22)
print(people_with_age)
{'Alice': 20, 'Bob': 21} 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:
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 = a
b = copy_list(a)
b.append(2)
print(a) # prints [0, 1]
print(b) # prints [0, 1, 2]
[0, 1] [0, 1, 2]
# vytvoření kopie seznamu:
a = [0, 1]
# b = a [ : ]
# b = a.copy()
# b = list(a)
b.append(2)
print(a) # prints [0, 1]
print(b) # prints [0, 1, 2]
[0, 1] [0, 1, 2, 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 = {} # copy = dict()
for key in source:
copy[key] = source[key]
# 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
).
def copy(source):
if isinstance(source, list):
copy = []
for element in source:
copy.append(element)
return copy
elif isinstance(source, set):
copy = set()
for x in source:
copy.add(x)
return copy
elif isinstance(source, dict):
copy = {}
for key in source:
copy[key] = source[key]
return copy
else:
# pokud nevíme, jak vytvořit kopii, vrátíme alespoň odkaz
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.append([4]) # modifikuje jen s2
print(s1)
print(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], [2, 3], [4]] [[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:
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 x in source:
copy.add(deep_copy(x))
return copy
elif isinstance(source, dict):
copy = {}
for key in source:
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:
def f(a, b, c, *args, k1, k2, **kwargs):
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, k1=10, k2=20, hello="world")
args: () kwargs: {} args: (3,) kwargs: {'k3': 30} args: (3, 4) kwargs: {'hello': 'world'}
def f(*args):
print(type(args), args)
for x in args:
print(x, end = ", ")
f("a","b","c",1,2,3)
<class 'tuple'> ('a', 'b', 'c', 1, 2, 3) a, b, c, 1, 2, 3,
def f(**kwargs):
print(type(kwargs), kwargs)
for x,y in kwargs.items():
print(x, "-", y, end = ", ")
print("")
f(a= "ahoj",b=1)
d = {"a":"A", "b":"B", "c":"C"}
f(**d)
<class 'dict'> {'a': 'ahoj', 'b': 1} a - ahoj, b - 1, <class 'dict'> {'a': 'A', 'b': 'B', 'c': 'C'} a - A, b - B, c - C,
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.
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, end = " ")
print("")
print_sequence(1,2,3,4)
s = [0, 1, 2]
print_sequence(s)
print_sequence(*s)
print_sequence(0,1,2)
1 2 3 4 [0, 1, 2] 0 1 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ů:
s2 = [10, 20, *s, 30]
print(s2)
[10, 20, 0, 1, 2, 30]
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, 2, c=3)
f(1, b=2, c=3)
# f(a=1, b=2, c=3)
a = 1 b = 2 c = 3 a = 1 b = 2 c = 3
cv 10/ Příklad 6.¶
- Napište funkci, která pomocí Eratosthenova síta najde všechna prvočísla menší nebo rovna $n$.
- vyzkoušejte si postupně různé pomocné struktury pro uložení earatosthenova síta: seznam s čísly, seznam s boolskými hodnotami, množinu, případně slovník
# Řešení pomocí množiny:
def eratosthenes(n):
# pocatecni množina (aratosthenovo síto) obsahuje všechna čísla od 2 do n:
sito = set(range(2,n+1))
# vezmu z množiny nejmenší číslo (určitě je to prvočíslo):
for i in range(2,n+1): # stačilo by ... range(2,int(n**0.5)+1)
if i in sito:
# odstraním z množiny všechny násobky i:
for j in range(2*i,n+1,i): # stačilo by ... range(i*i,n+1,i)
if j in sito:
sito.remove(j)
# na základě množiny vytvořím seznam:
seznam = list(sito)
return seznam
print(eratosthenes(50))
assert eratosthenes(12) == [2, 3, 5, 7, 11]
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
# Řešení pomocí seznamu:
def eratosthenes(n):
# pocatecni seznam (aratosthenovo síto) obsahuje všechna čísla od 0 do n:
sito = list(range(n+1))
# 1 není prvočíslo
sito[1]= 0
# vezmu ze seznamu nejmenší nenulové číslo (určitě je to prvočíslo):
for i in range(2,n+1): # stačilo by ... range(2,int(n**0.5)+1)
if sito[i] != 0:
# všechny násobky i změním na nulu:
for j in range(2*i,n+1,i): # stačilo by ... range(i*i,n+1,i)
sito[j]=0
# vytvořím výsledný seznam (bez nul):
seznam = []
for x in sito:
if x > 0:
seznam.append(x)
return seznam
print(eratosthenes(50))
assert eratosthenes(12) == [2, 3, 5, 7, 11]
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]