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

Práce se základními moduly standardní knihovny¶

Na dnešním cvičení se budeme podrobně zabývat klíčovým konceptem v jazyce Python Python – moduly.

Moduly vznikly z potřeby uchovávat definice funkcí a proměnných tak, aby byly dostupné i po ukončení běhu Python interpreteru. Když ukončíte a znovu spustíte Python interpreter, ztratíte všechny definice, které jste vytvořili. Pro psaní delších programů je tak lepší využít externích souborů a tím vytvořit skripty. A jak se váš program stává komplexnějším, může být výhodné rozdělit ho do několika souborů pro snazší údržbu a přehlednost.

V praxi to znamená, že můžete vytvářet moduly obsahující funkce, třídy nebo proměnné a následně je importovat do dalších modulů nebo skriptů. Tento přístup usnadňuje správu kódu a umožňuje opakované využití kódu bez kopírování definic do každého nového programu. To je zvláště užitečné při psaní funkcí, které chcete používat v různých projektech, aniž byste museli opisovat jejich definici do každého programu znovu.

Dnes se budeme zaměřovat na praktický pohled na několik standardních modulů, jako jsou math, random, sys, os, a další, abychom se s nimi naučili pracovat.

Příkaz import¶

Příkaz import je základním nástrojem, jak zavést moduly a jejich funkcionalitu do našeho kódu. Tento příkaz má několik variant.

Základní Import¶

import module_name

Tímto způsobem získáme přístup ke všem prvkům definovaným v module_name. Používá se jednoduše přístupem k prvkům přes přes tečkovou notaci - module_name.element

Import konkrétních prvků¶

Pokud nepotřebujeme importovat celý modul, ale jen několik jeho prvků (funkcí, tříd, konstant), můžeme to udělat následujícím způsobem

from module_name import element1, element2

K těmto prvkům již můžeme přistupovat napřímo, bez tečkové notace a plného názvu modulu - element1

Alternativní způsoby importu¶

- Alternativní pojmenování - alias¶

Během importu je možné modul nebo jeho prvky (záleží, která varianta importu je použita) pojmenovat alternativním názvem a pod tímto názvem s ním dále pracovat. Hodí se to převážně, pokud název modulu nebo jeho prvků je příliš dlouhý nebo komplikovaný nebo pokud by daný název kolidoval s již jinými objekty v našem programu.

import module_name as alias
from another_module import element1 as el1, element2 as el2

- Import všech prvků z modulu (❗❗NEDOPORUČOVÁNO❗❗)¶

Existuje možnost importovat všechny prvky z daného modulu.

from module_name import *

Tento přístup není doporučován, protože může dojít ke konfliktům názvů (k zastínění starších objektů) a dalším nejasnostem a k "zaplevelení" aktuálního namespace. Je doporučeno být explicitní v tom, jaké moduly a prvky z nich potřebuji a importuji. Pro rychlé prototypování v interaktivním intepretu to může být užitečné, ale ve finálním programu by se to nemělo používat.

In [3]:
# příklad

# Základní import
import math

# Import s aliasem
import numpy as np

# Import konkrétních prvků
from random import randint, choice as volba

# Použití importovaných prvků
print(math.sqrt(25))
print(np.array([1, 2, 3]))
print(randint(1, 10))
print(volba(['a', 'b', 'c']))
5.0
[1 2 3]
10
a
In [5]:
# ukázka ... konflikt názvů
pi = "ahoj"
print(pi)

from math import pi
print(pi)

pi = "ahoj"
print(pi)
ahoj
3.141592653589793
ahoj

Co se děje na pozadí příkazu import¶

Kdyz se zavolá některá z variant příkazu import, dojde k tomu, že importovaný objekt se stane součástí namespace, ve kterém je příkaz import zavolán. Nejčastěji import voláme na začátku programu v globálním namespace a importované objekty tak jsou součástí tohoto globálního namespace a dostupné odkudkoli v programu. Je možné ale import volat i v lokálním namespace (například v těle funkce) a pak efekt příkazu import je platný jen v tomto kontextu.

sys.modules¶

Při provádění příkazu import se Python nejprve podívá, jestli už se hledaný modul program nepokusil načíst již dříve (je jedno, jestli celý modul, nebo jen nějaký prvek z něj). Odkazy na moduly, které již program během svého běhu načetl, se nacházejí v sys.modules. Je to slovník, kde klíče jsou názvy modulů a hodnoty jednotlivé moduly. Slouží jako cache.

Pokud se tedy hledaný modul najde v tomto slovníku, nemusí se modul již načítat (už je v paměti načten) a do aktuálního namespace se vloží odkazy na modul, nebo na prvky, které z tohoto modulu chceme používat.

Pokud není nalezen, Python prohledává určité cesty v souborovém systému na disku a snaží se najít odpovídající moduly. Když se modul podaří nalézt, načte se do paměti, uloží do sys.modules a zveřejní v lokálním namespace.

Pokud není modul nalezen ani na souborovém systému, dojde k vyvolání výjimky ModuleNotFoundError (existuje ještě výjimka ImportError, která je rodičovská třída k třídě ModuleNotFoundError, a ta může být také vyvolána, pokud import selže z nějakých jiných důvodů)

In [6]:
# modul nebyl nalezen
import hfd
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[6], line 1
----> 1 import hfd

ModuleNotFoundError: No module named 'hfd'
Ukázka sys.modules¶

Pojďme se podívat na to, jak se import projevuje na aktuálním namespace a sys.modules.

In [3]:
import sys
print(type(locals()), type(globals()), type(sys.modules))
print(len(locals()), len(globals()), len(sys.modules))
<class 'dict'> <class 'dict'> <class 'dict'>
27 27 954
In [5]:
if 'x' in locals():
    print(locals()['x'] is pi)
print(locals().keys())
dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__builtin__', '__builtins__', '_ih', '_oh', '_dh', 'In', 'Out', 'get_ipython', 'exit', 'quit', 'open', '_', '__', '___', '__session__', '_i', '_ii', '_iii', '_i1', 'sys', '_i2', '_i3', '_i4', '_i5'])
In [3]:
# pomocna funkce
def filter_keys(keys):
    return [key for key in keys if not key.startswith('_')]
In [4]:
# jen objekty, co nezačínají _
keys = locals().keys()
print(filter_keys(keys))
['In', 'Out', 'get_ipython', 'exit', 'quit', 'open', 'filter_keys', 'keys']
In [5]:
# locals() a globals() jsou zde identické
print('GLOBAL', filter_keys(globals()), end='\n\n')
print('LOCAL', filter_keys(locals()), end='\n\n')

import sys
import turtle as zelva
from collections import Counter

print("Po importech:\n")

# podivejme se na lokalni a globalni kontext - jsou stejne, jsme totiz v globalnim kontextu
# vypiseme pro prehlednost jen klice relevantni klice
print('GLOBAL', filter_keys(globals()), end='\n\n')
print('LOCAL', filter_keys(locals()), end='\n\n')
print('jsou identicke?', globals() is locals(), end='\n\n')

# vidime zde moduly/funkce vsazene do namespace, aby byly k dispozici
print(Counter([1,1,2,3]), end='\n\n')
GLOBAL ['In', 'Out', 'get_ipython', 'exit', 'quit', 'open', 'filter_keys', 'keys']

LOCAL ['In', 'Out', 'get_ipython', 'exit', 'quit', 'open', 'filter_keys', 'keys']

Po importech:

GLOBAL ['In', 'Out', 'get_ipython', 'exit', 'quit', 'open', 'filter_keys', 'keys', 'sys', 'zelva', 'Counter']

LOCAL ['In', 'Out', 'get_ipython', 'exit', 'quit', 'open', 'filter_keys', 'keys', 'sys', 'zelva', 'Counter']

jsou identicke? True

Counter({1: 2, 2: 1, 3: 1})

In [22]:
# Podivejme se treba na modul turtle. Je nacteny v pameti a reference na nej se objevuje
# ve slovniku sys.modules pod jeho originalnim nazvem
print('Modul turtle je v sys.modules pod originalnim nazvem:', 'turtle' in sys.modules, end='\n\n')
print('V glob. ns je dostupny pod aliasem:', 'zelva' in globals(), end='\n\n')
print('Je to ten stejny modul?:', globals()['zelva'] is sys.modules['turtle'], end='\n\n')
Modul turtle je v sys.modules pod originalnim nazvem: True

V glob. ns je dostupny pod aliasem: True

Je to ten stejny modul?: True

In [11]:
# jak vypada samostatna funkce/trida, ktera byla importovana
print('Counter je dostupny v namespace:', 'Counter' in globals(), end='\n\n')
print('Counter je dostupny v cache - sys.module:', 'Counter' in sys.modules, end='\n\n')

print('V sys.modules je dostupny jeho modul, ne samotna fce:', 
      'collections' in sys.modules, end='\n\n')

print('je Counter v ns totez, jako Counter ukryty v modulu v sys.modules?:', 
      globals()['Counter'] is sys.modules['collections'].Counter, end='\n\n')
Counter je dostupny v namespace: True

Counter je dostupny v cache - sys.module: False

V sys.modules je dostupny jeho modul, ne samotna fce: True

je Counter v ns totez, jako Counter ukryty v modulu v sys.modules?: True

Poznámka: operátor is je operátor identity - říká, jestli 2 proměnné odkazují na ten stejný objekt v paměti

In [24]:
# Podívejme se ještě na lokální namespace, například ve funkci
def import_test():
    # naimportujeme neco, co jsme jeste dnes neimportovali
    import cmath
    x = 8
    print('CMATH sqrt jde pouzit: ',cmath.sqrt(-1), end='\n\n')
    print('GLOBAL ve funkci', filter_keys(globals().keys()), end='\n\n')
    print('LOCAL ve funkci ', filter_keys(locals().keys()), end='\n\n')
    print('jsou identicke?', globals() is locals(), end='\n\n')


#zjistime jestli cmath je v sys.modules
print('Cmath in sys.modules', 'cmath' in sys.modules)
print('Cmath in globals()', 'cmath' in globals())
# spustime funkce
import_test()

print('Cmath in sys.modules', 'cmath' in sys.modules)
print('Cmath in globals()', 'cmath' in globals())
# vykonavani spadne, v globalnim ns to neni importovane
print(cmath.sqrt(-1))
Cmath in sys.modules True
Cmath in globals() False
CMATH sqrt jde pouzit:  1j

GLOBAL ve funkci ['In', 'Out', 'get_ipython', 'exit', 'quit', 'open', 'debugpy', 'math', 'np', 'randint', 'volba', 'pi', 'sys', 'filter_keys', 'zelva', 'Counter', 'import_test']

LOCAL ve funkci  ['cmath', 'x']

jsou identicke? False

Cmath in sys.modules True
Cmath in globals() False
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[24], line 21
     19 print('Cmath in globals()', 'cmath' in globals())
     20 # vykonavani spadne, v globalnim ns to neni importovane
---> 21 print(cmath.sqrt(-1))

NameError: name 'cmath' is not defined

Výběr modulů standardní knihovny¶

math¶

Modul math poskytuje matematické funkce pro pokročilé výpočty. Obsahuje metody pro operace s čísly, trigonometrii, logaritmy a další matematické operace. Knihovna je velice bohatá a nabízí široké spektrum funkcí.

S touto knihovnou jsme se již seznámili v dřívějších cvičeních.

  • Práce s integery a konverze Funkce | Krátký popis -------------|--------------------------------------------------- math.ceil(x) | Zaokrouhlení nahodu math.floor(x)| Zaokrouhlení dolu math.trunc(x)| Odříznutí desetinné části

  • Obecné výpočty Funkce | Krátký popis -------------|--------------------------------------------------- math.factorial(n) | Výpočet n! math.gcd(*integers)| Výpočet největšího společného dělitele čísel math.prod(iterable, *, start=1)| Výpočet produktu - vzájemné vynásobení všech čísel

  • Trigonometrické a běžné funkce Funkce | Krátký popis -------------|--------------------------------------------------- math.exp(x)| Výpočet e^x math.log(x[, base] )| Výpočet logaritmu od základu base math.pow(x, y)| Výpočet mocniny x^y math.sqrt(x) | Výpočet odmocniny

  • Konstanty Konstanta | Krátký popis -------------|--------------------------------------------------- math.pi| Ludolfovo číslo pi - 3.14 math.e )| Eulerovo číslo e math.inf| Hodnota reprezentující kladné nekonečno math.sqrt(x) | Výpočet odmocniny

  • Zjišťovací funkce Funkce | Krátký popis -------------|--------------------------------------------------- math.isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0)| Zjistí, jestli jsou si čísla blízko za daných mezí (relativních nebo absolutních)

Více podrobností v oficiální dokumentaci

cmath¶

Tento modul poskytuje funkce podobné, jako v modulu math. Umí však počítat v komplexním prostoru. Navíc nabízí nějaké funkce aplikovatelné jen v komplexní rovině.

Funkce Krátký popis
cmath.phase(x) Fáze komplexního čísla
cmath.polar(x) Konverze komplexního čísla do polárních souřadnic
cmath.rect(r, phi) Konverze komplexního čísla z polárních do kartézských souřadnic

Více podrobností v oficiální dokumentaci

In [7]:
import math
import cmath

print(cmath.sqrt(-1))  # komplexní varianta umi vypočítat odmocninu záporných čísel
#print(math.sqrt(-1))

print((-1)**0.5)
1j
(6.123233995736766e-17+1j)
In [9]:
# ukázka isclose
x = cmath.sqrt(-1)
y = (-1)**0.5
print(x == y)
cmath.isclose(x, y)
False
Out[9]:
True

random¶

Modul random slouží k generování pseudo-náhodných čísel. Pomocí různých metod může být využit v různých situacích, například při hraní her nebo simulacích. Dostupné funkce se řadí do několika kategorií:

Sekce pro integery¶

Funkce Krátký popis
random.randrange(stop) Vrátí náhodný prvek z množiny hodnot, které by vygenerovala funkce range se stejnými parametry. Používá stejné parametry.
random.randint(a, b) Vrátí náhodný integer I tak, že a <= I <=b

Sekce pro sekvence¶

Funkce Krátký popis
random.choice(seq) Vybere náhodný prvek ze sekvence
random.shuffle(x) Náhodně zamíchá (inplace) prvky modifikovatelné sekvence

Sekce pro reálná čísla¶

Funkce Krátký popis
random.random() Vrátí náhodné číslo v r rozmezí 0.0 <= r < 1.0
random.uniform(a, b) Vrátí náhodné číslo v r rozmezí a <= r <= b

Seed¶

Někdy je velice užitečné být schopen zreplikovat posloupnost (pseudo)náhodných čísel, například kvůli ladění programu. Generátor náhodné sekvence funguje tak, že na základě aktuální hodnoty se generuje hodnota následující. Automatická volba počáteční hodnoty pak zajistí "náhodnost". Typicky se prvotní hodnota nastavuje například na aktuální čas nebo si bere počáteční hodnotu z nějakého systémového generátoru pseudo-náhodných čísel (/dev/random, /dev/urandom, ...).

Můžeme ale nastavit výchozí hodnotu na konkrétní číslo a tím zajistíme opakovatelnost - posloupnost náhodných čísel bude začínat vždy ze stejného bodu. Nezapomeňte vlastní seed pak odstranit před reálným použitím programu :)

Více podrobností v oficiální dokumentaci.

In [3]:
import random

# vygenerujeme nahodna cisla z mnoziny, kterou by vytvoril range(5, 50, 5)

print(random.randint(100, 200))


for i in range(5):
    print(random.randrange(5, 50, 5), end = " ")
129
35 30 10 30 10 

TIP: Zkuste předchozí kód pouštět vícekrát - pokaždé dá jiné hodnoty.

In [4]:
# vybereme nahodny prvek ze sekvence
s = [1,2,3,4,5,6,7,8,9,10]
print(random.choice(s))
print(random.choice(s))
print(random.choice(s))
print(random.choice(s))

print(s)
random.shuffle(s)
print(s)
9
4
4
7
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[2, 6, 7, 10, 9, 3, 8, 4, 5, 1]
In [5]:
# vygenerujeme nahodne cislo z daneho rozsahu
print(random.uniform(100, 200), random.random())
132.35304402217116 0.6652249794216701
In [6]:
# zazalohuje me si aktualni stav random objektu
random_state = random.getstate()
In [9]:
# nastavime vlastni seed - uvidime, ze to vzdy generuje stejne posloupnosti pri kazdem behu, pokud nastavime vlastni seed

random.seed(2)  # nejake konstantni cislo
for i in range(10):
    print(random.random())
0.9560342718892494
0.9478274870593494
0.05655136772680869
0.08487199515892163
0.8354988781294496
0.7359699890685233
0.6697304014402209
0.3081364575891442
0.6059441656784624
0.6068017336408379
In [10]:
# obnovime random do puvodniho stavu
random.setstate(random_state)

TIP: zkuste měnit seed a pozorujte, jak se mění výstup.

sys¶

Modul sys poskytuje funkce a hodnoty, které Python interpreter využívá nebo ho ovlivňují. Umožňuje například manipulaci s cestami nebo předávání argumentů při spouštění skriptu.

Důležité funkce a hodnoty¶

Funkce Krátký popis
sys.exit([arg]) Ukončí běh skriptu s daným návratovým kódem
sys.modules Slovník obsahující aktuálně načtené moduly. Slouží jako cache pro jejich načítání
sys.version_info Poskytuje informace o aktuální verzi interpreteru Python
sys.argv Seznam argumentů, předaných skriptu při spuštění

Více podrobností v oficiální dokumentaci.

In [11]:
import sys
# parametry, se kterymi byl tento skript spusten
print(sys.argv, end='\n\n')

print(sys.version_info)
['/usr/lib/python3.12/site-packages/ipykernel_launcher.py', '-f', '/home/user/.local/share/jupyter/runtime/kernel-8ea5017a-b262-4c19-8798-391e1889fe8a.json']

sys.version_info(major=3, minor=12, micro=7, releaselevel='final', serial=0)

os¶

Modul os poskytuje funkce pro interakci se systémovým prostředím, jako je manipulace s adresáři, spouštění příkazů nebo práce s proměnnými prostředí. Slouží jako rozhraní ke komunikaci s operačním systémem.

Důležité funkce a hodnoty - práce s proměnnými prostředí a systémem¶

Funkce Krátký popis
os.environ Slovník obsahující proměnné prostředí a jejich hodnoty. Může být využit pro konfiguraci našeho skriptu
os.getenv(key, default=None) Funkce schopna vrátit hodnoty proměnnných prostřed (s možností definovat defaultní hodnoty v případě nenalezení daného klíče). Operuje nad os.environ
os.system(command) Spustí příkaz v command v terminálu

Důležité funkce a hodnoty - práce se soubory¶

Funkce Krátký popis
os.getcwd() Zjistí aktuální adresář
os.chdir(path) Změní aktuální adresář na path
os.mkdir(path) Vytvoří nový adresář path. Neumí vytvořit více úrovní složek najednou
os.makedirs(path) Vytvoří nové adresáře. Umí vytvořit celou cestu adresářů.
os.rmdir(path) Smaže adresář path
os.listdir(path='.') Vrátí seznam adresářů a souborů v path, ve výchozím stavu v aktuálním adresáři
os.replace(src, dst) Přejmenuje/přesune soubor nebo složku src na dst
os.remove(path) Odstraní soubor path

Více podrobností v oficiální dokumentaci.

In [13]:
#import os
#print(filter_keys(os.environ))
In [2]:
import os
print(os.getenv("USER"))

# python funkce na vylistovani obsahu slozky
print(os.listdir('data'))

# zavolani alternativni funkce dostupne primo v terminalu operacniho systemu. Pouzijte dir pro windows OS.
# Ukazuje univerzalnost python funkci (vyse) a nezavislost na OS.
os.system('ls data')
None
['example.csv', 'readme.exe', 'touch.html', 'example-files', 'pokemon.txt', '.ipynb_checkpoints', 'text.txt', 'example.txt', 'rename.txt', 'inventory.txt', 'inventory.csv', 'testing-folder', 'argparse_test.py', 'data.txt', 'ukazka_argparse.py']
argparse_test.py
data.txt
example-files
example.csv
example.txt
inventory.csv
inventory.txt
pokemon.txt
readme.exe
rename.txt
testing-folder
text.txt
touch.html
ukazka_argparse.py
Out[2]:
0

Ukázka: Obsah souboru muj.py:

import sys

print(sys.argv, end='\n\n')

if len(sys.argv)> 1 and sys.argv[1] == '15':
    sys.exit(1)```
In [3]:
os.system('python muj.py')
['muj.py']

Out[3]:
0
In [8]:
os.system('python muj.py 1 b')
os.system('python muj.py 15 -a 45 a b')
['muj.py', '1', 'b']

['muj.py', '15', '-a', '45', 'a', 'b']

Out[8]:
256

Ukázka spuštění skriptu z terminálu:

0 user@jupyter-reitezuz:~/zpro-2024-public.git (main *>)$ python muj.py
['muj.py']

0 user@jupyter-reitezuz:~/zpro-2024-public.git (main *>)$ python muj.py a b
['muj.py', 'a', 'b']

0 user@jupyter-reitezuz:~/zpro-2024-public.git (main *>)$ python muj.py 15 a b
['muj.py', '15', 'a', 'b']

1 user@jupyter-reitezuz:~/zpro-2024-public.git (main *>)$ ```

pprint¶

Modul pprint (pretty-print) slouží k "krásnému" formátování výstupu, což je užitečné zejména při práci s komplexními datovými strukturami.

Důležité funkce a hodnoty¶

Funkce Krátký popis
pprint.pprint(object) Zajistí hezčí a přehlednějsí výpis komplikovaných datových struktur.

Pro zjednodušení práce je možné funkcí pprint nahradit původní funkci print

from pprint import pprint as print

Případně alternativně se zachováním původní funkce

import pprint
original_print = print
print = pprint.pprint

Více podrobností v oficiální dokumentaci.

In [15]:
import pprint
d = {i: i**2 for i in range(20)}
print("Standardni print")
print(d)
print("\nPretty print")
pprint.pprint(d)
Standardni print
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100, 11: 121, 12: 144, 13: 169, 14: 196, 15: 225, 16: 256, 17: 289, 18: 324, 19: 361}

Pretty print
{0: 0,
 1: 1,
 2: 4,
 3: 9,
 4: 16,
 5: 25,
 6: 36,
 7: 49,
 8: 64,
 9: 81,
 10: 100,
 11: 121,
 12: 144,
 13: 169,
 14: 196,
 15: 225,
 16: 256,
 17: 289,
 18: 324,
 19: 361}
In [52]:
import pprint

# nahrazeni
original_print = print
print = pprint.pprint

print(d)

# vraceni
print = original_print
{0: 0,
 1: 1,
 2: 4,
 3: 9,
 4: 16,
 5: 25,
 6: 36,
 7: 49,
 8: 64,
 9: 81,
 10: 100,
 11: 121,
 12: 144,
 13: 169,
 14: 196,
 15: 225,
 16: 256,
 17: 289,
 18: 324,
 19: 361}

collections¶

Modul collections poskytuje alternativní datové typy k vestavěným typům jako seznamy nebo slovníky. Tyto dodatečné datové typy mohou usnadnit práci při implementaci například některých algoritmů.

Důležité funkce a hodnoty¶

Funkce Krátký popis
collections.Counter([iterable-or-mapping]) Struktura typu slovník. Prvky původní posloupnosti jsou uloženy jako klíče, hodnoty značí počet výskyt§
collections.defaultdict() Je to slovník s výchozí hodnotou pro neexistující prvky
collections.namedtuple(typename, field_names) Alternativa k tuple. Umožní pojmenovat jednotlivé pozice pro jednodušší a přehlednější použití

Více podrobností v oficiální dokumentaci.

In [16]:
from collections import Counter
data = [1, 2, 3, 1, 2, 1, 4, 5]
counter = Counter(data)
print(counter)

data = "abrakadabra"
counter = Counter(data)
print(counter)
Counter({1: 3, 2: 2, 3: 1, 4: 1, 5: 1})
Counter({'a': 5, 'b': 2, 'r': 2, 'k': 1, 'd': 1})
In [18]:
p = (1, 2)
x, y = p
print(p[0], p[1])

from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)
print(p.x, p.y)  # Vytiskne hodnoty x a y

# funguje i originalni "tuple" přístup přes indexy
print(p[0], p[1])
1 2
1 2
1 2
In [17]:
from collections import defaultdict
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]

d = defaultdict(list)

# výhoda je v tom, že nemusíme ověřovat, jestli daný klíč je ve slovníku
# pro každý neexistující klíč se při přístupu automaticky vytvoří s danou defaultní hodnotou 
# list v tomto případě
for k, v in s:
    d[k].append(v)
print(d.items())
dict_items([('yellow', [1, 3]), ('blue', [2, 4]), ('red', [1])])
defaultdict(<class 'list'>, {'yellow': [1, 3], 'blue': [2, 4], 'red': [1]})
In [18]:
# ukázka, jak by to bylo s obyčejným slovníkem:
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]

d = {}
for k, v in s:
    if k not in d:
        d[k] = []
    d[k].append(v)
    
print(d)
{'yellow': [1, 3], 'blue': [2, 4], 'red': [1]}
In [21]:
# varianta s počtem:
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]

d = defaultdict(int)
for k, v in s:
    d[k] += v
print(d)
defaultdict(<class 'int'>, {'yellow': 4, 'blue': 6, 'red': 1})
In [20]:
s = "abrakadabra"
d = defaultdict(int)
for x in s:
    d[x] += 1
print(d)
defaultdict(<class 'int'>, {'a': 5, 'b': 2, 'r': 2, 'k': 1, 'd': 1})

itertools¶

Modul itertools poskytuje funkce pro vytváření efektivních iterátorů, které jsou užitečné jak samostatně, tak ve vzájemné kombinaci. Obsahuje funkce například pro slučování a kombinaci více zdrojů, opakování, nebo filtrování.

Důležité funkce a hodnoty¶

Funkce Krátký popis
itertools.count(start=0, step=1) Iterator určený k očíslování. Užitečný například u ZIPu nebo map funkci
itertools.cycle(iterable) Umožňuje nekonečnou iteraci přes nějakou posloupnost
itertools.chain(*iterables) Vytvoří iterátor, který postupně iteruje přes všechny posloupnosti
itertools.product(*iterables, repeat=1) Vygeneruje prvky kartézského součinu všech vstupních posloupností
itertools.permutations(iterable, r=None) Vygeneruje permutace o délce r
itertools.combinations(iterable, r) Vygeneruje kombinace o délce r
itertools.combinations_with_replacement(iterable, r) Vygeneruje kombinace s opakováním o délce r

Více podrobností v oficiální dokumentaci.

In [20]:
import itertools
c = itertools.count()

for i in range(20):
    print(next(c), end = " ")
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
In [21]:
for i in itertools.count():
    print(i, end = " ")
    if i > 10:
        break
0 1 2 3 4 5 6 7 8 9 10 11 
In [22]:
i = 0
for x in itertools.cycle("abc"):
    print(x, end = " ")
    i += 1 
    if i > 10:
        break
        
# Nekonecna smycka tisknouci 1,2,3,1,2,3,1,2,3,.....
#for i in itertools.cycle( [1,2,3]):
#    print(i)
a b c a b c a b c a b 
In [28]:
for i in itertools.product('abc', [1,2,3,4]):
    print(i)
('a', 1)
('a', 2)
('a', 3)
('a', 4)
('b', 1)
('b', 2)
('b', 3)
('b', 4)
('c', 1)
('c', 2)
('c', 3)
('c', 4)
In [25]:
for i in itertools.product('abc', repeat=2):
    print(i)
('a', 'a')
('a', 'b')
('a', 'c')
('b', 'a')
('b', 'b')
('b', 'c')
('c', 'a')
('c', 'b')
('c', 'c')
In [26]:
pole = [1,2,3,4,5]
#  ukázka kombinací o delce 2
print(list(itertools.combinations(pole, 2)))

#  ukázka kombinací o delce 3
print(list(itertools.combinations(pole, 3)))
[(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)]
[(1, 2, 3), (1, 2, 4), (1, 2, 5), (1, 3, 4), (1, 3, 5), (1, 4, 5), (2, 3, 4), (2, 3, 5), (2, 4, 5), (3, 4, 5)]

csv¶

Modul csv poskytuje funkcionalitu pro práci s formátem CSV (Comma-Separated Values), což je často používaný formát pro ukládání tabulkových dat.

Důležité funkce a hodnoty¶

Funkce Krátký popis
csv.reader(csvfile, dialect='excel', **fmtparams) Vytvoří objekt pro čtení dat ze souboru CSV
csv.writer(csvfile, dialect='excel', **fmtparams) Vytvoří objekt pro zápis dat do souboru CSV.

Více podrobností v oficiální dokumentaci.

In [28]:
with open('data/inventory.csv', 'r') as file:
    print(file.read())
#name,amount,price
Kýbl,5,85
Pytel cementu,10,120
Cihla,65,25
Kladivo,3,150
Vrtačka,2,2000
Hřebík,560,0.5
Míchačka,1,5600
Hmoždinka,120,3
In [30]:
import csv

with open('data/inventory.csv', 'r') as file:
    reader = csv.reader(file)
    for row in reader:
        print(row)  # Vytiskne každý řádek ze souboru CSV
['#name', 'amount', 'price']
['Kýbl', '5', '85']
['Pytel cementu', '10', '120']
['Cihla', '65', '25']
['Kladivo', '3', '150']
['Vrtačka', '2', '2000']
['Hřebík', '560', '0.5']
['Míchačka', '1', '5600']
['Hmoždinka', '120', '3']
In [29]:
# ukázka navíc: zápis csv:
import csv

# Otevření souboru pro zápis CSV
with open('data/example.csv', 'w') as csvfile:
    # Vytvoření objektu csv.writer
    csv_writer = csv.writer(csvfile)

    # Zápis prvního řádku - hlavičky
    csv_writer.writerow(['#Jméno', 'Příjmení', 'Věk'])

    # Zápis dat
    csv_writer.writerow(['Jan', 'Novák', 25])
    csv_writer.writerow(['Eva', 'Svobodová', 30])
    csv_writer.writerow(['Petr', 'Dvořák', 22])
    
with open('data/example.csv', 'r') as file:
    print(file.read())
#Jméno,Příjmení,Věk
Jan,Novák,25
Eva,Svobodová,30
Petr,Dvořák,22

json¶

Modul json umožňuje práci s formátem JSON (JavaScript Object Notation), což je populární formát pro výměnu dat mezi aplikacemi.

Důležité funkce a hodnoty¶

Funkce Krátký popis
json.dumps(obj) Převede objekt na string ve formátu JSON (serializace).
json.loads(json_str) Převede JSON řetězec zpět na objekt (deserializace).

Více podrobností v oficiální dokumentaci.

In [31]:
import json

json_str = '{"name": "John", "age": 30, "city": "New York"}'  # vyzaduje dvojite uvozovky
loaded_data = json.loads(json_str)  # nacteme string JSON a udelame z nej python objekt
print(type(loaded_data))
print(loaded_data)  # vytiskne python objekt odpovidajici strukture JSON (dict)

data = [5, 7, 8]
json_str = json.dumps(data)  # vytvori json retezec z python objektu

print(json_str)  # Vytiskne JSON řetězec
<class 'dict'>
{'name': 'John', 'age': 30, 'city': 'New York'}
[5, 7, 8]
In [32]:
# ukázka navíc: zápis a načítání JSON do/ze souboru
import json

d = {"name": "John", "age": 30, "city": "New York"}

# Otevření souboru pro zápis JSON
with open('data/example.json', 'w') as json_file:
    json.dump(d, json_file)

# Otevření souboru pro čtení JSON
with open('data/example.json', 'r') as json_file:
    new_d = json.load(json_file)

print(new_d)
{'name': 'John', 'age': 30, 'city': 'New York'}

argparse¶

argparse je velmi užitečný modul v jazyce Python, který umožňuje jednoduché a efektivní zpracování příkazové řádky. Tento modul slouží k definování, konfiguraci a parsování argumentů a jejich hodnot spouštěného skriptu. To usnadňuje usnadňuje psaní rozsáhlejších Python skriptů.

Hlavní vlastnosti argparse¶
  • umožňuje definovat, jaké argumenty může skript nebo aplikace přijímat. Argumenty mohou být povinné nebo volitelné.
  • Každý argument může mít přidružené parametry, například typ dat, který očekává (řetězec, číslo, atd.), nebo zkrácenou a plnou verzi názvu argumentu
  • postará se o správu chyb spojených s nevalidními vstupy nebo chybějícími argumenty
  • generuje automaticky nápovědu a dokumentaci pro všechny definované argumenty, což zjednodušuje orientaci uživatelům.
  • poskytuje možnost specifikovat vzájemné vztahy mezi argumenty, například vyžadování určitých kombinací argumentů nebo nastavení výchozích hodnot.
Stručné použití¶

Vytvoříme novou instanci parseru a nastavíme popis programu, případně dovětek dokumentace

import argparse

parser = argparse.ArgumentParser(
                    description='What the program does',
                    epilog='Text at the bottom of help')

Následně můžeme přidávat jednotlivé argumenty. Funkce add_argument má velké množství konfiguračních parametru, viz doc

parser.add_argument('filename')           # positional argument
parser.add_argument('-c', '--count')      # option that takes a value
parser.add_argument('-v', '--verbose',
                    action='store_true')  # on/off flag

Spustíme parser. Následně můžeme přistupovat k hodnotám jednotlivých argumentů

args = parser.parse_args()
print(args.filename, args.count, args.verbose)

Více k ukázce dole v příkladech.

Více podrobností v oficiální dokumentaci

Externí (nestandardní) balíčky a moduly¶

Kromě standardních modulů, které jsou součástí každé instalace Pythonu, existuje bohatý ekosystém externích modulů, nazývaných balíčky. Tyto balíčky poskytují širokou škálu funkcionalit, které mohou být snadno integrovány do projektů. Instalace externích balíčků je obvykle prováděna pomocí správce balíčků, například pip.

Zde je několik příkladů externích balíčků:

  • requests - Slouží k jednoduchému provádění HTTP requestů
  • numpy - Poskytuje podporu pro efektivní manipulaci s multidimenzionálními poli a matematickými funkcemi.
  • Pandas - Nabízí vysokoúrovňové datové struktury a nástroje pro analýzu dat
  • Django - webový framework pro vývoj moderních webových aplikací v jazyce Python

TIP: Používání externích balíčků se věnuje navazující předmět 18PPY1.

Příklady¶

1) Vzdálenost bodů v rovině¶

Napište program, který který bude analyzovat vzdálenosti mezi body v rovině. Bude obsahovat nejméně 2 funkce:

  • def average_distance(points) - spočítá průměrnou vzájemnou vzdálenost bodů. Vrátí tuto hodnotu
  • def closest_points(points) - zjistí, které 2 body jsou si nejblíže. Vrátí tuple, který bude obsahovat tyto 2 body

Může se hodit funkce itertools.combinations.

Proměnná point bude posloupnost bodů. Volbu toho, jak reprezentovat bod, nechám na Vás. Nabízím ale 2 možné varianty, které procvičí látku dnešní hodiny:

  1. namedtuple('Point', ['x', 'y'])
  2. reprezentace pomocí komplexních čísel - tam se pak může hodit operátor mínus (-) a funkce abs

Sadu bodů, které budete testovat si vygenerujte náhodně. Pro začátek vyrobte 10 bodů tak, aby se nacházely v intervalu <0,100> pro souřadnici x i y. Bude se hodit funkce random.uniform. Pro ladění programu se může hodit zafixovat generátor pseudonáhodných čísel na konkrétní počáteční hodnotě (random.seed(value))

In [19]:
# volné místo na program
import random
import itertools
import collections


p = (1, 2)  # body jako ntice
print(p)
print(p[0], p[1]) # přístup ke složkám

print(random.uniform(0, 100))  # náhodné číslo

p = (random.uniform(0, 100), random.uniform(0, 100)) # náhodný bod
print(p)
(1, 2)
1 2
59.31837303800576
(39.3599686377914, 17.034919685568127)
In [15]:
# body jako ntice
p = (1, 4)
print(p[0], p[1])

n = 3
points = [(random.uniform(0, 100), random.uniform(0, 100)) for i in range(n)]
print(points, "\n")
1 4
[(38.12042376882124, 21.659939713061338), (42.21165755827173, 2.9040787574867943), (22.169166627303504, 43.7887593650572)] 

In [18]:
# body jako pojmenované ntice
from collections import namedtuple
Point = collections.namedtuple('Point', ['x','y'])
p = Point(1, 4)
print(p.x, p.y)

n = 3
points = [Point(random.uniform(0, 100), random.uniform(0, 100)) for i in range(n)]
print(points, "\n")
1 4
77.84426150001458 52.09384176131452
[Point(x=39.325509496422605, y=48.96935204622582), Point(x=2.9574963966907064, y=4.3487290356527435), Point(x=70.3382088603836, y=98.3187717309674)] 

In [22]:
# kombinace
print(list(itertools.combinations("abcd", 2)))
#print(list(itertools.combinations(points, 2)))
[('a', 'b'), ('a', 'c'), ('a', 'd'), ('b', 'c'), ('b', 'd'), ('c', 'd')]
[(Point(x=39.325509496422605, y=48.96935204622582), Point(x=2.9574963966907064, y=4.3487290356527435)), (Point(x=39.325509496422605, y=48.96935204622582), Point(x=70.3382088603836, y=98.3187717309674)), (Point(x=2.9574963966907064, y=4.3487290356527435), Point(x=70.3382088603836, y=98.3187717309674))]
In [34]:
# Řešení pomocí namedtuple:
import random
import itertools
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])

def distance(point1, point2):
    return ((point1.x-point2.x)**2 + (point1.y-point2.y)**2)**0.5
    # return ((point1[0]-point2[0])**2 + (point1[1]-point2[1])**2)**0.5

def average_distance(points):
    sum_distances = 0
    count_distances = 0
    for point1, point2 in itertools.combinations(points, 2):
        sum_distances += distance(point1, point2)
        count_distances +=  1
    return sum_distances/count_distances
    
def average_distance1(points):
    distances = []
    for point1, point2 in itertools.combinations(points, 2):
        distances.append(distance(point1, point2))
    return sum(distances) / len(distances)
    
def average_distance2(points):
    distances = [distance(point1, point2) for  point1, point2 in itertools.combinations(points, 2)]
    return sum(distances) / len(distances)

def closest_points(points):
    min_points =  (points[0], points[1])
    min_distance = distance(points[0], points[1])
    for point1, point2 in itertools.combinations(points, 2):
        d = distance(point1, point2)
        if d < min_distance:
            min_distance = d
            min_points = (point1, point2)
    return min_points
    
def closest_points(points):
    distances = [(distance(point1, point2), point1, point2) for  point1, point2 in itertools.combinations(points, 2)]
    return min(distances)
    

print(distance(Point(1, 1), Point(-1, 0)))
random.seed(2)
points = [Point(random.uniform(0, 100), random.uniform(0, 100)) for i in range(10)]
print(average_distance(points), average_distance1(points), average_distance2(points))
print(closest_points(points))
2.23606797749979
52.68497366091925 52.68497366091925 52.68497366091925
(12.609145799787424, Point(x=43.066964029126865, y=39.35318202053713), Point(x=44.485418872585356, y=26.82407416493281))

2) Hluboká adresářová struktura¶

Napište program, který ve vámi zvoleném adresáři vytvoří stromovou strukturu adresářů. Struktura je parametrizována její hloubkou (počet úrovní zanoření) a množstvím větvení (kolik nových podstromů je v každé úrovni).

Názvy adresářů záleží na Vás. Můžete používat například čísla nebo písmena. Ukázka pro faktor větvení 2 a hloubku 3.

├── 1
│   ├── 1
│   │   ├── 1
│   │   └── 2
│   └── 2
│       ├── 1
│       └── 2
└── 2
    ├── 1
    │   ├── 1
    │   └── 2
    └── 2
        ├── 1
        └── 2

Následně zkuste pomocí os.system zavolat příkaz tree <cesta_k_root_directory> pro vizualizaci vaší struktury. Nezapomeňte <cesta_k_root_directory> nahradit skutečnou hodnotou.

Nakonec strukturu po sobě smažte.

In [2]:
# volné místo na program
def generate_tree(root_directory, branch_factor, depth):
    assert depth <= 6, 'Structure too deep'
    assert branch_factor <= 6, 'Structure too wide'
    # your code here

3) Opilá želva¶

Naprogramujte opilou želvu. Taková želva se pohybuje po obrazovce náhodným způsobem. Náhodně se otáčí a chodí náhodnou vzdálenost.

Poznámka: želvu je potřeba spouštět v nativním Pythonu na vašem nebo školním počítači. Zde v Jupyteru to nelze.

In [1]:
# volné místo na program
import turtle
import random

# Nastavení želvy
drunk_turtle = turtle.Turtle()
drunk_turtle.shape("turtle")
drunk_turtle.speed(2)  # Rychlost pohybu želvy

# Funkce pro náhodný pohyb želvy
def drunk_walk(turtle, steps=50, step_length=20):
    for _ in range(steps):
        # Náhodný úhel
        angle = random.uniform(0, 360)
        turtle.left(angle)

        # Náhodná vzdálenost
        distance = random.uniform(0, step_length)
        turtle.forward(distance)

# Spustit opilou želvu
drunk_walk(drunk_turtle)

# Uzavření okna po kliknutí
turtle.exitonclick()

4) Argparse testování¶

Otevřete si soubor argparse_test.py a prohlédněte si obsah. Můžete ho pustit přímo z terminálu, nebo níže pomocí os.system Experimentujte s různými parametry, které skriptu předáte.

Dobrovolně zkuste program modifikovat tak, aby mohl přijmout neomezené množství čísel a všechna sečíst. Může se hodit action='append' z dokumentace.

In [55]:
# Spustime tento soubor bez parametru parametry
import os
os.system("python3 data/argparse_test.py")
usage: argparse_test.py [-h] [-s] [-r] cislo1 cislo2
argparse_test.py: error: the following arguments are required: cislo1, cislo2
Out[55]:
512
In [56]:
# výpis nápovědy
os.system("python3 data/argparse_test.py --help")
usage: argparse_test.py [-h] [-s] [-r] cislo1 cislo2

Program pro výpočet součtu a rozdílu.

positional arguments:
  cislo1        První číslo pro výpočty
  cislo2        Druhé číslo pro výpočty

options:
  -h, --help    show this help message and exit
  -s, --soucet  Spočítat součet čísel
  -r, --rozdil  Spočítat rozdíl čísel
Out[56]:
0
In [57]:
os.system("python3 data/argparse_test.py 1 2")
Není vybrána žádná operace.
Out[57]:
0
In [58]:
os.system("python3 data/argparse_test.py 1 2 --soucet")
Součet: 3
Out[58]:
0
In [60]:
os.system("python3 data/argparse_test.py 1 2 -r -s")
Součet: 3
Rozdíl: -1
Out[60]:
0
In [38]:
# volné místo na program

5) Argparse - program na modifikaci souborů¶

Napište program, který s použitím modulu argparse bude očekávat argument --input-file, kde mu předáme cestu k nějakému textovému souboru. Program daný soubor načte a na obrazovku vypíše modifikovaný obsah - vše velkými písmeny. Soubory na testování můžete nalézt například ve složce data, která byla využívána během cvičení 19, kdy jsme se učili, jak pracovat se soubory.

Program napište do zvláštního souboru s příponou .py. Můžete buď zde na JupyterHubu nebo na vašich počítačích v prostředí VSCodium. Spouštět takový program je nejlépe ručně z příkazové řádky, kdy můžete jednoduše specifikovat argumenty při spuštění. Terminál je dostupný i zde na Jupyteru.

In [39]:
# volné místo na program

6) Argparse - program na modifikaci souborů #2¶

Rozšiřte předchozí program tak, že bude obsahovat další volitelný parametr --output-file. Pokud bude použit, program by měl výstup uložit do tohoto souboru.

Experimentujte s dalšími možnostmi argparse.

In [40]:
# volné místo na program