From 0a7363e3c2b2880a5effd23441e1ddb36135217f Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 30 Mar 2020 16:34:12 +0200 Subject: [PATCH] Add lesson on interfaces --- lessons/beginners/interfaces/index.md | 111 +++++++++++++++ lessons/beginners/interfaces/info.yml | 4 + lessons/beginners/interfaces/static/tkui.py | 146 ++++++++++++++++++++ lessons/beginners/interfaces/static/yn.png | Bin 0 -> 7180 bytes 4 files changed, 261 insertions(+) create mode 100644 lessons/beginners/interfaces/index.md create mode 100644 lessons/beginners/interfaces/info.yml create mode 100644 lessons/beginners/interfaces/static/tkui.py create mode 100644 lessons/beginners/interfaces/static/yn.png 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 0000000000000000000000000000000000000000..ae922071a68393dbfdd90ddeb222d8760fba6da0 GIT binary patch literal 7180 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 literal 0 HcmV?d00001