Konverzace s uživatelem

O čem si budeme povídat?

Naše programy zatím používaly pouze statická data. Taková data jsme mohli, pokud jsme to potřebovali, prozkoumat ještě před spuštěním programu, takže jsme program mohli napsat tak, aby jim vyhovoval. Ale většina programů taková není. Většina programů očekává, že budou řízeny uživatelem. Přinejmenším očekávají, že jim uživatel sdělí jaký soubor mají otevřít, upravit jeho obsah a podobně. Jiné programy si v kritických místech od uživatele vyžádají vstup dat. A právě to se v programování nazývá uživatelským rozhraním. Návrhem a budováním uživatelského rozhraní se v komerčních programech zabývají specialisté, kteří byli školeni v oblastech styku člověka se strojem (human-machine interaction) a ergonomie. Běžný programátor je takového luxusu ušetřen, takže se musí řídit citem a musí pečlivě zvažovat, jakým způsobem budou uživatelé program používat.

Nejzákladnějším rysem uživatelského rozhraní je zobrazování výstupu. S jeho nejprimitivnější formou jsme se již seznámili v podobě pythonovského příkazu print (a také v podobě funkce write() jazyka JavaScript a v podobě dialogu MsgBox z jazyka VBScript). Další krok v návrhu uživatelského rozhraní spočívá v přímém získávání vstupu od uživatele. Nejjednodušší způsob, jak to zařídit, spočívá v tom, že se program za běhu na vstupní údaj zeptá. Druhý nejjednodušší způsob vyžaduje od uživatele, aby potřebné informace zadal při spuštění programu (jako parametry programu). A konečně se dostáváme i ke grafickému uživatelskému rozhraní (GUI = Graphical User Interface [grafikl júzr interfejs]) s různými vstupními poli a dalšími prvky. V této části se podíváme na první dvě metody. S programováním grafického uživatelského rozhraní se seznámíme v této učebnici až mnohem později, protože jde o problematiku výrazně složitější.

Při využití interaktivního režimu systému Python v okně IDLE nebo v terminálovém okně operačního systému si nyní ukážeme, jak lze od uživatele získat data. Poté si ukážeme, jak lze totéž provést přímo z programu.

>>> print raw_input(u"Něco napište: ")[1]

Poznámka překladatele: Při pokusech s českými texty v interaktivním režimu můžete narazit na problémy. Při pokusech proto doporučuji nepoužívat řetězce v kódování Unicode (viz předchozí odkaz na podrobnější poznámku). V interaktivním režimu se raději úplně vyhněte používání českých znaků. Pokud ovšem kód uložíte do souboru a dodržíte pravidla pro práci s řetězci v kódování Unicode, měly by programy korektně fungovat.

Jak můžete pozorovat, raw_input() jednoduše zobrazí zadanou výzvu — v našem případě "Něco napište: " — a zachytí vše, co uživatel napíše jako odpověď. Příkaz print pak tuto odpověď zobrazí. Odpověď bychom ale mohli místo zobrazení přiřadit do proměnné:

>>> odpoved = raw_input(u"Jak se jmenuješ? ")
>>> print u"Tak ty se jmenuješ %s! Jsem rád, že jsem tě poznal." % odpoved

Příkaz raw_input() má bratrance jménem input(). Rozdíl spočívá v tom, že raw_input() sesbírá uživatelem napsané znaky a chápe je jako textový řetězec, zatímco input() se z nich bude snažit vytvořit číslo. Pokud například uživatel napíše znaky '1', '2' a '3', pak input tyto tři znaky přečte a převede je na číslo 123.

Použijme nyní příkaz input() k tomu, aby uživatel rozhodl, která tabulka násobků se má tisknout:

nasobitel = input(u"Vyberte hodnotu násobitele: ")
for j in range(1, 13):
    print "%d x %d = %d" % (j, nasobitel, j * nasobitel)

Použití příkazu input je naneštěstí velmi záludné. Je to dáno tím, že input() se nesnaží vyhodnocovat jen čísla, ale snaží se na libovolný vstup pohlížet jako na kód v jazyce Python a snaží se jej provést. To znamená, že by znalý uživatel se zákeřnými úmysly mohl napsat příkaz jazyka Python, který by například vymazal nějaký soubor na vašem PC! Z tohoto důvodu bude lepší, když se budete držet příkazu raw_input() a výsledný řetězec si budete převádět na potřebný datový typ použitím zabudovaných konverzních funkcí jazyka Python. Je to v podstatě velmi snadné:

>>> nasobitel = int(raw_input(u"Vyberte hodnotu násobitele: "))
>>> for j in range(1, 13):
...    print "%d x %d = %d" % (j, nasobitel, j * nasobitel)

Vidíte? Volání raw_input() jsme jednoduše obalili voláním int(). Efekt je stejný, jako kdybychom použili input(), ale je to mnohem bezpečnější. Existují i jiné konverzní funkce, takže uživatelský vstup můžete stejně dobře převádět na reálná čísla a na další typy.

Takže co kdybychom si to vyzkoušeli v opravdovém programu? Vzpomínáte si na příklady realizující adresář lidí, které využívaly strukturu typu slovník? Zabývali jsme se jimi v rámci tématu Data, datové typy a proměnné. Podívejme se na tento příklad znovu, v situaci, kdy již známe cykly a umíme číst vstup od uživatele.

# Vytvoříme prázdný slovník používaný jako adresář osob.
adresy = {}

# Načítáme jeho položky, dokud není zadán prázdný řetězec.
print
jmeno = raw_input(u'Zadejte jméno (nic => konec): ')
while jmeno != '':
    polozka = raw_input(u'Zadejte ulici, město, telefon (nic => konec): ')
    adresy[jmeno] = polozka
    jmeno = raw_input(u'Zadejte jméno (nic => konec): ')
    
# Teď se budeme ptát, co se má zobrazit.
jmeno = raw_input(u'Které jméno se má zobrazit? (nic = konec): ')
while jmeno != '':
    print jmeno + ':', adresy[jmeno]
    jmeno = raw_input(u'Které jméno se má zobrazit? (nic = konec): ')

Prozatím je to náš nejrozsáhlejší program. Uživatelské rozhraní je sice trochu neučesané, ale funguje. V dalších tématech uvidíme, jak je můžeme vylepšit. U tohoto programu se zmiňme o jedné věci, a sice o boolovském testu v příkazu cyklu while. Podle jeho výsledku se určuje, zda chce uživatel ukončit činnost (zda se má ukončit provádění cyklu). Povšimněte si také, že datovou část uchováváme v podobě jednoho řetězce, zatímco v příkladu v části Data, datové typy a proměnné jsme informaci uchovávali v samostatných buňkách. Zatím jsme se totiž nezabývali tím, jak bychom mohli řetězec rozdělit na několik částí. Dostaneme se k tomu v pozdějších tématech. S programem implementujícím adresář osob se dále v učebnici ještě setkáme. Postupně jej budeme upravovat do užitečnější podoby.

Vstup v jazyce VBScript

V jazyce VBScript se vstup zadávaný uživatelem čte příkazem InputBox. Můžeme psát:

<script type="text/vbscript">
Dim vstup
vstup = InputBox("Zadejte své jméno") 
MsgBox ("Zadali jste: " & vstup)
</script>

Funkce InputBox() zobrazí dialogové okno s textem výzvy a s polem pro zadávání vstupu. Zadaný obsah tohoto pole získáme jako návratovou hodnotu funkce. Při volání můžeme funkci navíc předat další parametry, jako je například řetězec zobrazovaný v titulku okna. Pokud uživatel stiskne tlačítko Storno (Cancel) vrací funkce prázdný řetězec nezávisle na tom, co bylo zadáno do vstupního pole.

Následující příklad ukazuje implementaci adresáře osob v jazyce VBScript.

<script type="text/vbscript">
Dim adresy, jmeno, polozka ' Vytvoříme proměnné.
Set adresy = CreateObject("Scripting.Dictionary")
jmeno = InputBox("Zadejte jméno", "Položka adresáře osob")
While jmeno <> ""
    polozka = InputBox("Zadejte detaily - ulice město, telefon", "Položka adresáře osob")
    adresy.Add jmeno, polozka   ' Přidej klíč a detaily.
    jmeno = InputBox("Zadejte jméno", "Položka adresáře osob")
Wend

' Teď se budeme ptát, co se má zobrazit.
jmeno = InputBox("Zadejte jméno", "Zobrazení údaje z adresáře osob")
While jmeno <> ""
   MsgBox(jmeno & " - " & adresy.Item(jmeno))
   jmeno = InputBox("Zadejte jméno", "Zobrazení údaje z adresáře osob")
Wend
</script>

Základní struktura progamu se zcela shoduje s Pythonovskou verzí, až na pár řádků navíc. VBScript vyžaduje, aby byly proměnné předem deklarovány příkazem Dim. Každý příkaz cyklu musí být navíc ukončen příkazem Wend.

Čtení vstupu v jazyce JavaScript

JavaScript pro nás předtavuje určitou výzvu, protože jde o jazyk, který se používá především pro webovské prohlížeče. Jako takový nemá žádný speciální příkaz pro vstup. Místo toho můžeme číst z HTML elementu form. V prohlížeči Internet Explorer můžeme případně použít technologii Active Scripting firmy Microsoft a nechat si zobrazit dialog InputBox stejně, jako jsme to udělali v jazyce VBScript. Abychom se seznámili s různými možnostmi, ukáži zde techniku využívající HTML formuláře. Pokud vám pojem HTML formulář nic neříká, můžete nahlédnout do referenční příručky HTML (viz norma HTML 4.01) nebo do nějaké učebnice, která jej vysvětluje. Můžete také jednoduše okopírovat to, co uvádím dále. Doufám, že vysvětlování významu ani není třeba. Slibuji, že se to budu snažit načrtnout co nejjednodušším způsobem.

Základem struktury našeho příkladu v HTML bude javascriptový kód vložený do funkce. Zatím jsme se s tím ještě nesetkali. Prozatím můžete detaily kolem definice funkce ignorovat.

<script type="text/javascript">
function mujProgram(){
    alert("Získali jsme hodnotu " + document.formular.pole.value);
}
</script>

<form name='formular'>
<p>Zadej hodnotu a potom klikni myší mimo vstupní pole</p>
<input type='text' name='pole' onChange='mujProgram()'>
</form>

Program se skládá z jediného řádku, který zobrazí dialog alert (velmi se podobá MsgBox() v jazyce VBScript). V něm se zobrazuje hodnota získaná ze vstupního pole. Ve formuláři je zobrazen vyzývací text (uzavřený do párových značek <p> a </p>) a vstupní pole. V kontextu dokumentu document má formulář jméno formular. Vstupní pole jsme pojmenovali jednoduše pole. Na jeho hodnotu, která byla vložena uživatelem, se tedy můžeme v javascriptovém programu odkázat takto:

document.formular.pole.value

Příklad implementace našeho adresáře osob v JavaScript zde ukazovat nebudu. Vzhledem k použití v HTML bude vše o něco složitější a zvýší se i četnost používání funkcí. S příkladem tedy chci počkat až do doby, kdy potřebnou problematiku probereme v samostatných tématech.

Pár slov o stdin a stdout

Poznámka: Slovem stdin se v počítačovém žargonu označuje standardní vstupní zařízení (obvykle klávesnice). Slovo stdout se vztahuje ke standardnímu výstupnímu zařízení (obvykle k obrazovce). V diskusích o programování se s termíny stdin a stdout setkáváme poměrně často. Abychom mohli využít kód pro práci se soubory, je to zařízeno tak, že se stdin a stdout chovají jako soubory.

V systému Python jsou stdin a stdout součástí modulu sys a jmenují se sys.stdin a sys.stdout. Funkce raw_input() používá automaticky stdin, příkaz print používá zase stdout. Ze standardního vstupu můžeme číst a na standardní výstup můžeme zapisovat také přímo. Má to určité výhody ve smyslu jemnějšího řízení vstupu a výstupu. Uveďme si příklad čtení jednoho řádku ze standardního vstupu:

import sys
print "Zadej hodnotu: ",       # čárka zabrání přechodu na další řádek
hodnota = sys.stdin.readline() # explicitní použití stdin
print hodnota

Funkčnost se téměř shoduje s chováním příkazu:

print raw_input("Zadej hodnotu: ")

Výhodou explicitního použití stdin je to, že standardní vstup můžeme svázat se skutečným souborem, takže program svůj vstup nebude číst z klávesnice, ale z daného souboru. Tento obrat je užitečný při realizaci dlouhých testů programu, kdy místo ručního vkládání vstupních údajů v okamžiku, kdy je program požaduje, necháme vstup přečíst z předem připraveného souboru. (Další výhodou je v takovém případě skutečnost, že test můžeme spouštět opakovaně, přičemž jsme si jisti, že vstup bude pokaždé stejný, takže by tomu měl odpovídat stejný výstup. Tuto techniku opakovaného spouštění dřívějších testů, které mají ověřit, že se nic nepokazilo, programátoři nazývají regresní testování.)

Na závěr si ukažme příklad přímého výstupu na standardní výstupní zařízení sys.stdout. Tento výstup může být rovněž přesměrován do fyzického souboru. Funkčnost příkazu print se přibližně shoduje s funkčností následujícího zápisu:

sys.stdout.write("Ahoj, vy tam!\n") # \n = nový řádek

V praxi tento obrat použijeme hlavně v případech, kdy chceme obejít vlastnost příkazu print, který vždy vkládá mezi výstupní hodnoty alespoň mezeru. Při použití stdout se tomu můžeme vyhnout. Srovnejte obsah dvou řádků na výstupu následujícího příkladu:

import sys
for polozka in ['jedna', 'je', 1]: 
    print polozka,                      # čárka potlačí přechod na nový řádek
print 
for polozka in ['jedna', 'je', str(1)]: # musíme explicitně převést na řetězec
    sys.stdout.write(polozka)           # zcela bez mezer!

Poznámka překladatele — výstup by měl vypadat takto:

jedna je 1
jednaje1

Pokud předem víme, jak budou data vypadat, pak stejného výsledku můžeme samozřejmě dosáhnout i použitím formátovacího řetězce. Ale pokud to nevíme, pak je jednodušší jednoduše posílat vše na stdout, než abychom se snažili o generování složitého formátovacího řetězce za běhu programu.

Přesměrování stdin a stdout

Takže jak vlastně můžeme přesměrovat stdin a stdout z a do souborů? Můžeme toho dosáhnout přímo z našeho programu, když použijeme běžné pythonovské techniky pro práci se soubory (budeme se tím zabývat za chvíli). Ale nejjednodušší způsob spočívá ve využití vlastností operačního systému.

Vyzkoušejte si, jak funguje jeden z příkazů operačního systému, když na příkazovém řádku předepíšeme přesměrování:

C:\> dir
C:\> dir > dir.txt

První z příkazů vypíše obsah adresáře na obrazovku, druhý jej zapíše do souboru. Použitím znaku '>' programu říkáme, že chceme stdout přesměrovat do souboru dir.txt.

Totéž bychom mohli udělat s našim pythonovským programem:

$ python mujprogram.py > vysledek.txt

Poznámka překladatele: Vyzývací znak '$' v uvedeném příkladu napovídá, že jde o použití v unixovém systému (například v systému Linux). V systému MS Windows bychom to udělali úplně stejně.

Uvedený zápis vede ke spuštění mujprogram.py, ale jeho výstup by se místo na obrazovce objevil v souboru vysledek.txt. Jeho obsah si poté můžeme prohlédnout prostřednictvím nějakého textového editoru.

K přesměrování stdin z nějakého souboru jednoduše místo znaku '>' použijeme znak '<'. Následuje úplný příklad. Nejdříve vytvoříme soubor opisvstupu.py a vložíme do něj následující zdrojový text:

import sys
vstup = sys.stdin.readline()
while vstup.strip() != '':
    print vstup
    vstup = sys.stdin.readline()

Poznámka: Metoda strip() odsekne znak, který reprezentuje přechod na nový řádek. Ten je součástí načítaného řádku ze stdin. Příkaz raw_input() by to udělal za vás.

Poznámka překladatele: Metoda strip() ve skutečnosti odstraňuje všechny bílé znaky (whitespaces) ze začátku i z konce řetězce. To znamená, že odstraní všechny úvodní a koncové mezery, tabulátory a znaky přechodu na nový řádek. Při použití metody readline() ovšem můžeme získat řetězec, ve kterém se znak přechodu na nový řádek vyskytuje jenom jednou a vždy zcela na konci.

Teď si to můžeme vyzkoušet spuštěním z příkazového řádku:

$ python opisvstupu.py

Výsledkem by měl být program, který vše opisuje na výstup, dokud nezadáte prázdný řádek.

Teď si vytvořte jednoduchý textový soubor nazvaný vstup.txt, který bude obsahovat pár řádků textu. Spusťte program znovu a přesměrujte do něj vstup ze souboru vstup.txt:

$ python opisvstupu.py < vstup.txt

Python opisuje na výstup vše, co našel v souboru vstup.txt. Vzpomínáte si, že jsme si řekli, že příkaz print a standardní funkce raw_input() ve skutečnosti vnitřně pracují se stdin a stdout? To znamená, že v příkladu opisvstupu.py můžeme práci se stdin nahradit voláním raw_input() následovně:

vstup = raw_input()
while vstup != '':
    print vstup
    vstup = raw_input()

... což je ve většině případů mnohem jednodušší.

Postupným přesměrováním vstupu z více různých souborů můžeme rychle a snadno otestovat chování našich programů v různých situacích (například při zadávání špatných hodnot nebo údajů špatného typu). Můžeme tak učinit opakovatelným a spolehlivým způsobem. Techniku přesměrování můžeme použít také při zpracování velkých objemů dat z připravených souborů. Přitom se při používání stejného programu nezbavujeme možnosti ručního vstupu dat v případech, kdy je objem dat malý. Mechanismus přesměrování stdin a stdout představuje pro programátora velmi užitečný trik. Zkoušejte a uvidíte sami, jaká další použití se vám podaří najít.

Parametry z příkazového řádku

Další typ vstupu představuje vstup zadaný z příkazového řádku. Dejme tomu, že nějak takto spustíte svůj textový editor z příkazového řádku:

$ EDIT Foo.txt

Operační systém spustí program nazvaný EDIT a předá mu jméno souboru, který se má editovat — v našem případě Foo.txt. Ale jak si editor přečte jméno souboru?

Ve většině jazyků systém poskytuje pole nebo seznam řetězců, které obsahují slova z příkazového řádku. Takže první prvek bude obsahovat jméno příkazu, druhý prvek bude obsahovat první argument, atd. Někdy máme k dispozici další magickou proměnnou (často je pojmenována argc z anglického argument count — počet argumentů), ve které je uložen počet prvků uvedeného seznamu.

V systému Python je zmíněný seznam součástí modulu sys a nazývá se argv (z anglického argument values, čili hodnoty argumentů). V Pythonu nepotřebujeme znát hodnotu argc, protože počet předaných argumentů můžeme zjistit jako délku (počet prvků) seznamu argv pomocí standardní funkce len(). Většinou nepotřebujeme dělat ani to, protože průchod celým seznamem můžeme předepsat pythonovským cyklem for takto:

import sys
for polozka in sys.argv:
    print polozka
    
print u'První argument byl:', sys.argv[1]

Poznamenejme, že to bude fungovat jen v případě, kdy uvedený kód uložíte do souboru (dejme tomu args.py) a spustíme jej z příkazového řádku operačního systému, například takto:

C:\Python\Projekty> python args.py 1 23 fred
args.py
1
23
fred
První argument byl: 1
C:\Python\Projekty>

VBScript a JavaScript

Tyto jazyky jsou určeny pro tvorbu webových stránek, takže možnost načítání parametrů zadaných na příkazovém řádku u nich nepřipadá v úvahu. Pokud bychom je používali v prostředí Microsoft Windows Script Host, situace by se změnila. WHS nabízí možnost extrakce těchto argumentů z objektu WshArguments, který je naplněn v době běhu.

A to je k tématu uživatelského vstupu opravdu vše, co budeme v této učebnici potřebovat. Je to sice velmi primitivní, ale přesto s tím vystačíte při psaní užitečných programů. V počátcích systému Unix nebo u prvních osobních počítačů představovala tato podoba interakce s uživatelem jedinou dostupnou možnost. V programech s grafickým uživatelským rozhraním můžeme samozřejmě vstup načítat také. K tomu, jak se to dělá, se ale v této učebnici dostaneme mnohem později.

Zapamatujte si

Pokud vás napadne, co by se dalo na překladu této kapitoly vylepšit, zašlete e-mail odklepnutím Tím budou do dopisu automaticky vloženy informace o tomto HTML dokumentu.

$Id: cztutinput.html,v 1.13 2005/09/03 13:07:56 petr Exp $