diff --git a/lessons/beginners/interfaces/index.md b/lessons/beginners/interfaces/index.md new file mode 100644 index 0000000000..9d365e52de --- /dev/null +++ b/lessons/beginners/interfaces/index.md @@ -0,0 +1,111 @@ +# Rozhraní + +Už víš že funkce ti umožňují kousek kódu: + +* použít (zavolat) na více místech v programu, i když definice je jen jedna, +* vyčlenit, aby detail (jako načtení čísla od uživatele) „nezavazel“ ve větším + programu, který tak může být přehlednější, a +* pojmenovat, aby bylo jasné co kód dělá i bez toho, abys musel{{a}} číst + samotné tělo funkce. + +Další výhoda funkce je, že ji můžeš jednoduše vyměnit za jinou, +lepší funkci – pokud má ta lepší funkce stejné *rozhraní* (angl. *interface*). + +Aby se ti líp představovalo, o čem budeme povídat, představ si elektrickou +zásuvku ve zdi. +Do takové zásuvky můžeš zapojit počítač, lampu, nabíječku na mobil, vysavač, +nebo rádio. +Zásuvka poskytuje elektrický proud; je jedno, jak ho použiješ. +Stejně tak je jedno jestli je „druhý konec“ zásuvky připojený k solárnímu +panelu nebo k atomové elektrárně. +Zásuvka poskytuje elektrický proud, a jsou u ní důležité určité parametry +(tvar, napětí, frekvence, maximální proud) na kterých se obě strany, +poskytovatel proudu i spotřebič, shodly. + + +# Funkce jako rozhraní + +Podívej se na tuhle hlavičku funkce. +Víš z ní, co ta funkce dělá a jak ji použít? + +```python +def ano_nebo_ne(otazka): + """Zeptá se uživatele na otázku a vrátí True nebo False dle odpovědi""" +``` + +Podobnou funkci už jsi napsala; víš že „vevnitř“ volá `input` a ptá se na +příkazové řádce. + +Co kdybys ale měla následující funkci? + +```python +def ano_nebo_ne(otazka): + """Ukáže tlačítka "Ano" a "Ne" a až uživatel jedno zmáčkne, vrátí True + nebo False dle stisknutého tlačítka.""" +``` + +Screenshot s tlačítky Ano a Ne + +Když zavoláš tuhle funkci, `ano_nebo_ne('Chutná ti čokoláda?')`, ukáže se +okýnko se dvěma tlačítky. +Když uživatel jedno zmáčkne, funkce vrátí True nebo False. + +Z hlediska programu se nic nemění: jediné co se změní je *definice funkce*; +volání je pak stejné jako dřív. + + +# Vyzkoušej si to! + +Najdi nějaký svůj program, který používá `ano_nebo_ne`, případně jen `print` +a `input`. + +Stáhni si modul tkui.py +do adresáře se svým programem. +Naimportuj z něho funkce, které potřebuješ. +Jsou k dispozici čtyři: + +```python +from tkui import input, nacti_cislo, ano_nebo_ne, print +``` + +Tento import *přepíše* vestavěné funkce `input` a `print` variantami, +které mají (téměř) stejné rozhraní – jen dělají něco trochu jinak. + +Případné vlastní definice funkcí `nacti_cislo` a `ano_nebo_ne` pak z programu +vyndej, aby se použily ty naimportované. + +Program by měl fungovat stejně jako dřív! + +Je to tím, že tyto funkce mají stejné rozhraní jako jejich dřívější protějšky, +tedy: + +* jméno, kterým se funkce volá, +* argumenty, které bere (např. `input` bere otázku jako řetězec; `print` + může bere více argumentů k vypsání), a +* návratovou hodnotu, se kterou program pracuje dál (např `input` vrací + řetězec; u `print` nevrací nic smysluplného). + +Většina z těchto informací je přímo v hlavičce funkce. +Ty ostatní je dobré popsat v dokumentačním řetězci, aby ten, kdo chce funkci +použít, věděl jako na to. + + +# Je to dobrý nápad? + +Modul `tkui` je jen ilustrační. Nedoporučuju ho používat. + +Příkazová řádka je dělaná tak, aby byla užitečná pro programátory. +Až se naučíš základy a vytvoříš nějaký skvělý program, přijde čas +k logice (tzv. *backendu*) přidat část, která bude lépe použitelná pro +uživatele – tedy okýnko nebo webovou stránku (tzv. *frontend*). + +Udělat hezké a funkční *uživatelské* rozhraní je ovšem většinou celkem složité, +a často se dělá až potom, co jsou samotné „vnitřnosti“ funkční a otestované. +Doporučuju postupovat stejně, když se programování učíš: zůstaň u základních +`print` a `input`, dokud nezvládneš samotné programování. +A pak se můžeš naučit něco nového! + +Co si ale z této lekce odnes je koncept rozhraní: při zachování několika +informací z hlavičky je možné vyměnit funkci za něco úplně jiného. +A stejně tak je možné jednu funkci (třeba `input`) volat ze spousty různých +programů, pokud znáš její rozhraní. diff --git a/lessons/beginners/interfaces/info.yml b/lessons/beginners/interfaces/info.yml new file mode 100644 index 0000000000..c4c4128aaa --- /dev/null +++ b/lessons/beginners/interfaces/info.yml @@ -0,0 +1,4 @@ +title: Rozhraní +style: md +attribution: Pro PyLadies Brno napsal Petr Viktorin, 2020. +license: cc-by-sa-40 diff --git a/lessons/beginners/interfaces/static/tkui.py b/lessons/beginners/interfaces/static/tkui.py new file mode 100644 index 0000000000..ee5ec311c7 --- /dev/null +++ b/lessons/beginners/interfaces/static/tkui.py @@ -0,0 +1,146 @@ +"""Modul s funkcemi pro okýnkové otázky a odpovědi: + +input(otazka) -> str + +nacti_cislo(otazka) -> int + +ano_nebo_ne(otazka) -> bool + +print(argument0, argument1, argument2, ..., argument_n, sep='') + +""" + +from tkinter import Tk, LEFT, RIGHT, BOTTOM, W +from tkinter.ttk import Label, Button, Spinbox, Entry + + +# Následující kód používá několik pokročilejších technik. +# Není důležité *jak* je kód napsaný, ale že je možné ho napsat – a že daná +# funkce má dané rozhraní :) +# +# Mimochodem; opravdové funkce input a print jsou ještě složitější; viz: +# https://github.com/python/cpython/blob/ce105541f/Python/bltinmodule.c#L1931 +# https://github.com/python/cpython/blob/ce105541f/Python/bltinmodule.c#L1827 + +# Funkce používají modul tkinter, který je zabudovaný v Pythonu, ale okýnka +# s ním vytvořená nevypadají příliš profesionálně. +# Budeš-li chtít začít psát "okýnkové" programy, doporučuji začít rovnou +# s knihovnou jako Qt nebo GTK. +# Na Qt máme mimochodem lekci v pokročilém kurzu: +# viz https://naucse.python.cz/course/mi-pyt/intro/pyqt/ + + +def input(otazka='odpověz'): + """Zeptá se uživatele na otázku a vrátí odpověď jako řetězec.""" + root = Tk() + root.title(otazka) + + button = Button(root, text="OK", command=root.quit) + button.pack(side=RIGHT) + + entry = Entry(root) + entry.pack(side=LEFT) + + root.mainloop() + + value = entry.get() + root.destroy() + + return value + + +def nacti_cislo(otazka='Zadej číslo'): + """Zeptá se uživatele na otázku a vrátí odpověď jako celé číslo.""" + root = Tk() + root.title(otazka) + + entry = Spinbox(root, from_=0, to=100) + entry.set('0') + entry.pack(side=LEFT) + + # Předbíháme: vnořená funkce může přistupovat + # k proměnným "entry" a "root", které jsou + # lokální pro "vnější" funkci (nacti_cislo) + + def ok_pressed(): + text = entry.get() + try: + value = int(text) + except ValueError: + entry.set('sem zadej číslo!') + else: + root.quit() + + button = Button(root, text="OK", command=ok_pressed) + button.pack(side=RIGHT) + + root.mainloop() + + value = int(entry.get()) + root.destroy() + + return value + + +def ano_nebo_ne(otazka='Ano nebo ne?'): + """Dá uživateli na výběr Ano/Ne a vrátí odpověď True nebo False.""" + root = Tk() + root.title(otazka) + + value = False + + # Předbíháme: "nonlocal" umožňuje *měnit* + # lokální proměnnou z vnější funkce. + + def yes(): + nonlocal value + value = True + root.quit() + + def no(): + nonlocal value + value = False + root.quit() + + button = Button(root, text="Ano", command=yes) + button.pack(side=LEFT) + + button = Button(root, text="Ne", command=no) + button.pack(side=RIGHT) + + root.mainloop() + root.destroy() + + return value + + + +# Předbíháme: hvězdička v *args umožní že "print" bere proměnný +# počet argumentů; přes "args" se potom dá projít příkazem "for" +def print(*args, sep=' ', end='', file=None, flush=False): + """Zobrazí dané argumenty.""" + root = Tk() + root.title('print') + + str_args = '' + for arg in args: + str_args = str_args + sep + str(arg) + + button = Label(root, text=str_args[len(sep):] + end) + button.pack(anchor=W) + + button = Button(root, text="OK", command=root.quit) + button.pack(side=BOTTOM) + + root.bind('', (lambda e: root.quit())) + root.mainloop() + root.destroy() + + +# A tady je trik, jak kousek kódu nespustit když se modul importuje. +# Pro opravdové programy ale doporučuji spouštěcí modul, viz kurz. +if __name__ == '__main__': + print(input()) + print(nacti_cislo()) + print(ano_nebo_ne()) + print('a', 'b', 'c', sep='; ', end='-') diff --git a/lessons/beginners/interfaces/static/yn.png b/lessons/beginners/interfaces/static/yn.png new file mode 100644 index 0000000000..ae922071a6 Binary files /dev/null and b/lessons/beginners/interfaces/static/yn.png differ