Subsections

 
7. Vstup a výstup

Každý program je vlastně předpis, transformační funkce, která určitým způsobem převádí vstupní data na výstupní. Celá toto kapitola bude věnována způsobům, kterými může program vstupní data získat a naopak jak může uživateli vrátit svoje výstupní data.

Program může výstupní data vrátit dvěma způsoby, buďto je naformátuje do určitého tvaru tak, aby je uživatel mohl bez problémů přečíst a zapíše je na standardní výstup, nebo výstupní data určitým způsobem zakóduje a uloží do souboru.7.1

 
7.1 Formátování výstupu

Čtenář, který se dostal až sem, jistě poznal dva způsoby, kterými lze na obrazovku vytisknout nějakou hodnotu. První, která přichází v úvahu pouze v případě interaktivního interpretru, je vyhodnocení výrazu, kdy interpretr vytiskne hodnotu tohoto výrazu na obrazovku. Druhou možnost, použitelnou všude, nabízí příkaz print. Ten vypíše argumenty jemu předané na standardní výstup.7.2

S největší pravděpodobností budete potřebovat větší kontrolu nad formátováním výstupu vašho programu. Samotný příkaz print vám nabízí pouhé vytisknutí hodnot, je-li jich více, oddělí je mezerou. Jak jistě cítíte, nebude to to pravé ořechové. Nyní máte dvě cesty, kterými se můžete dát, první - složitější - spočívá v napsání veškeré formátovací logiky, druhá - jednodušší - používá standardní modul string.

Python umožňuje převést libovolnou hodnotu na řetězec. K tomu slouží dvojice funkcí str() a repr(). Ekvivalentem poslední jmenované funkce je zapsání výrazu mezi obrácené apostrofy ``.

Funkce str() by měla vracet takovou reprezentaci argumentu, která je srozumitelná především pro člověka. Proto pro řetězce vrátí jejich čistou hodnotu, pro reálná čísla vrátí řetězec, obsahující desetinný rozvoj na 12 platných číslic.

Naproti tomu funkce repr() (a její ekvivalent v podobě obrácených apostrofů) vrací reprezentaci argumentu, která je vhodná pro interpretr. Čili vrací hodnotu tak, jak bychom jí zapsali do zdrojového souboru. Např. k řetězcům přidává úvodní a ukončovací uvozovky, reálná čísla vrací s přesností na 17 platných míst. Pro ty objekty, pro něž neexistuje čitelná reprezentace, vrací funkce repr() stejnou hodnotu jako str(), to platí především pro strukturované datové typy (seznamy, slovníky):

>>> s = 'Ahoj světe.'
>>> str(s)
'Ahoj světe.'
>>> `s`
"'Ahoj světe.'"
>>> str(0.1)
'0.1'
>>> `0.1`
'0.10000000000000001'
>>> x = 10 * 3.25
>>> y = 200 * 200
>>> s = 'Hodnota x je ' + `x` + ' a y ' + `y` + '...'
>>> print s
Hodnota x je 32.5 a y 40000...
>>> # Obrácené uvozovky je možné použít i s jinými typy:
... p = [x, y]
>>> ps = repr(p)
>>> ps
'[32.5, 40000]'
>>> # Konvertování řetězců přidá uvozovky a zpětná lomítka:
... ahoj = 'ahoj světe\n'
>>> ahojs = `ahoj`
>>> print ahojs
'ahoj světe\n'
>>> # Zpětné uvozovky můžeme použít i na tuple:
... `x, y, ('spam', 'eggs')`
"(32.5, 40000, ('spam', 'eggs'))"

Vraťme se však k problematice formátování výstupu programu. Nejprve si uvedeme krátký příklad - program vypisující tabulku druhých a třetích mocnin:

>>> import string
>>> for x in range(1, 11):
...     print string.rjust(`x`, 2), string.rjust(`x*x`, 3),
...     # Nezapomeňte ukončující čárku na předchozím rádku
...     print string.rjust(`x*x*x`, 4)
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

Tento příklad je ukázkou použití formátovací funkce string.rjust(), která zarovná řetězec mezerami vpravo, přičemž jako šířku sloupce použije druhý argument. Stejně tak existují i funkce string.ljust() a string.center().

Jde o standardní funkce, které pouze vrátí upravený řetězec. Pro vypsání jejich hodnoty musíme použít příkaz print. V předchozím příkladě si všimněte, jak příkaz print vložil mezi svoje argumenty mezery. (Pro zkrácení řádku jsme ho rozložili na dva, první příkaz print je ukončen čárku a nevypisuje tudíž znak konce řádku).

U všech funkcí, které ovlivňují zarovnání řetězce se setkáme s problémem, kdy obsah pole má větší šířku než požaduje programátor. Zde si zapamatujte, že výše uvedené funkce v tomto případě zachovávají původní řetězec, což sice pokazí vaše formátování, nicméně data zůstanou korektní. Pokud opravdu víte, že potřebujete výsledek pevně oříznout, použijte cosi jako "string.ljust(x, n)[0:n]".

Pro úplnost si uvedeme ještě čtvrtou funkci string.zfill(). Ta doplní řetězec, který reprezentuje nějaké číslo, zleva nulami na požadovanou šířku sloupce, přičemž správně interpretuje znaménko plus a mínus před číslem:

>>> import string
>>> string.zfill('12', 5)
'00012'
>>> string.zfill('-3.14', 7)
'-003.14'
>>> string.zfill('3.14159265359', 5)
'3.14159265359'

Pro účely formátování výstupu je možné použít i operátor %. Jak je vám zcela jistě známo, jde o operátor modulo (zbytek po dělení), nicméně, pokud na místě prvního operandu uvedete řetězec, změní se zcela jeho funkce. Začne totiž pracovat obdobně jako C funkce sprintf(), první řetězec je použit jako tzv. formátovací řetězec, přičemž výskyty speciálních znaků jako %s, %r a dalších jsou nahrazeny určitou hodnotou.

Nyní se výše uvedený výpis kódu pro vytištění tabulky druhých a třetích mocnin přepíšeme za použití operátoru %:

>>> for x in range(1,11):
...     print '%2d %3d %4d' % (x, x*x, x*x*x)
... 
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

Operátor % prochází řetězec - první argument a hledá v něm předlohy ve tvaru %<formát>. Posloupnost znaků <formát> specifikuje, jak bude interpretována hodnota předaná jako druhý operand. Může se určit jak konverzní funkce ("f" - převod z čísla v plovoucí řádové čárce na řetězec, "d" - převod číslo na řetězec), tak i formát výsledku, lze specifikovat celkovou šířku pole a počet číslic za desetinnou čárkou. Výsledek konverzní funkce bude "dosazen" do původního řetězce:7.3

>>> import math
>>> print 'Hodnota Ludolfova čísla je přibližně %5.3f.' % math.pi
Hodnota Ludolfova čísla je přibližně 3.142.

Budete-li chtít do jednoho řetězce dosadit více hodnot, musí být pravý operand tuple. To nám názorně ukazuje další příklad:

>>> tabulka = {'Adolf': 4127, 'Běta': 4098, 'Cyril': 7678}
>>> for jmeno, telefon in tabulka.items():
...     print '%-10s ==> %10d' % (jmeno, telefon)
... 
Běta       ==>       4098
Adolf      ==>       4127
Cyril      ==>       7678

Pro programátory v C bude dobrou zprávou, že formátovací sekvence operátoru % pracují většinou stejně jako v C. Pokud C funkci sprintf() předáte chybní argument, program většinou skončí s neoprávněným přístupem do paměti, zatímco v Pythonu se z tohoto místa "pouze" rozšíří výjimka.

I význam příkazu %s je mnohem volnější, originál v jazyce C připouštěl na místě argumentu pouze řetězec, zatímco v Pythonu můžete použít libovolný typ. Jeho řetězcovou reprezentaci operátor získá pomocí nám již důvěrně známé funkce str().7.4 Podobný pár tvoří příkaz %r a funkce repr().

Další variantou, kterou Python usnadňuje programátorovi život, je možnost se ve formátovacím řetězci (tj. tom, který stojí vlevo od operátoru %) odkazovat na položku nějakého slovníku, ten je operátoru předán jako druhý argument. Formátovací příkaz pak bude mít tvar %(jméno_klíče)<formát>:

>>> tabulka = {'Adolf': 4127, 'Běta': 4098, 'Cyril': 8637678}
>>> print 'Běta: %(Běta)d; Adolf: %(Adolf)d; Cyril: %(Cyril)d' % tabulka
Břéťa: 4098; Adolf: 4127; Cyril: 8637678

Takto můžeme velice "vkusně" vytisknout hodnotu nějaké proměnné. Využijeme přitom funkce vars(), která vrací slovník, obsahující všechny proměnné (klíče jsou jména proměnných, hodnoty pak skutečné hodnoty proměnných):

>>> ahoj = 'Vítáme Vás'
>>> print 'Máme pro vás následující zprávu: %(ahoj)s!' % vars()
Máme pro vás následující zprávu: Vítáme Vás!

 
7.2 Práce se soubory

Velice často programátor potřebuje přečíst/zapsat nějaká data z/do souboru. Pomineme-li možnost Unixového shellu, jenž dokáže přesměrovat standardní vstup a výstup programu, musí se o to programátor postarat sám.

Ve většině programovacích jazycích je soubor reprezentován jako souborový objekt, který obsahuje data o samotném souboru. Před jeho používáním programem ho musíme otevřít, čímž vytvoříme v paměti objekt reprezentující tento soubor. Při otevření specifikujeme i další potřebné informace - jméno souboru, mód přístupu (pouze ke čtení, pouze pro zápis, přidávání, čtení i zápis), velikost vyrovnávací paměti použité k přístupu k tomuto souboru atd.

Nový souborový objekt vytvoříme pomocí funkce open() 7.5. Tato funkce má jeden povinný argument - jméno souboru a další dva nepovinné, první z nich specifikuje mód přístupu (implicitně pouze pro čtení) a druhý velikost vyrovnávací paměti (tento údaj je důležitý pro knihovnu jazyka C, jež je použita pro implementaci práce se soubory):

>>> f=open('/tmp/workfile', 'w')
>>> print f
<open file '/tmp/workfile', mode 'w' at 80a0960>

Zůstaňme na chvíli u řetězce, reprezentujícího mód přístupu k souboru. První možností je řetězec 'r' určující přístup pouze pro čtení, v našem příkladě jsme použili mód 'w', který specifikuje přístup pouze pro zápis. Další možností je 'a', který otevře soubor pro přidávání, což je totéž jako zápis, jen data se budou přidávat na konec souboru. Dejte si pozor při používání módu 'w', pokud soubor existuje, budou data bez jakéhokoli varování smazána. Poslední mód 'r+' umožňuje čtení i zápis.

Na systémech Windows a Macintosh se setkáte ještě s modem 'b', který způsobí otevření souboru v binárním módu. Tyto systémy totiž rozlišují mezi textovými a binárními soubory. Z toho vyplývá, že exitují i módy jako 'rb', 'wb' a 'r+b'. Opomenutí znaku 'b' může způsobit nemalé problémy, proto se ho snažte používat i na platformách, které sice tento mód nepoužívají. Brzy byste totiž mohli chtít váš program portovat na jinou platformu a mohli byste se dočkat nemilých překvapení.

 
7.2.1 Čtení a zápis souborů

Předpokládejme, že existuje souborový objekt, pojmenovaný f. Tento objekt nám poskytuje několik metod (funkcí, které se vážou na tento objekt), pomocí nichž můžeme načítat obsah tohoto souboru.

První operací, kterou souborové objekty podporují je čtení jejich obsahu, k tomu slouží metoda f.read(). Jako argument jí můžeme předat i počet bytů, které se mají ze souboru přečíst. Výsledné byty vrátí jako řetězec. Pokud bylo při tomto volání f.read() dosaženo konce souboru, vrátí se pouze ty znaky, které v souboru zbývaly. Při opakovaném čtení za koncem souboru vrací tato metoda prázdný řetězec. Argument, specifikující počet bytů je volitelný, v případě, že se rozhodnete ho nespecifikovat, přečte se celý obsah souboru.

>>> f.read()
'Toto je obsah celého souboru.\n'
>>> f.read()
''

Metoda f.readline() přečte ze souboru jeden řádek a vrátí ho jako řetězec. Znak konce řádku '\n' je obsažen na konci řetězce. V případě čtení za koncem souboru vrací prázdný řetězec. To umožňuje programu rozlišit mezi prázdným řádkem a koncem souboru, prázdný řádek pak obsahuje jediný znak konce řádku.

>>> f.readline()
'Toto je první řádka souboru.\n'
>>> f.readline()
'Druhá řádka souboru\n'
>>> f.readline()
''

Užitečná metoda f.readlines() přečte několik řádků ze souboru a vrátí je jako seznam. Pokud jí program nepředá žádný argument, přečte všechny řádky, jinak pouze tolik bytů, kolik určuje argument a navíc ještě ty, které jsou třeba k dokončení aktuální řádky. Pro znak konce řádku platí stejná pravidla jako u metody f.readline().

>>> f.readlines()
['Toto je první řádka souboru.\n', 'Druhá řádka souboru\n']

Z pomoci metody f.write() můžeme do souboru zapisovat. Předáme jí vždy řetězec, který se zapíše do souboru přesně tak, jak je. Tato metoda vždy vrací hodnotu None.

>>> f.write('Zkouška mikrofonu - 1 ... 2 ... 3\n')

V souboru je možné se i pohybovat a zjišťovat aktuální pozici (tj. pozici, ze které bude probíhat další čtení a kam se budou zapisovat další data). Pomocí metody f.tell() můžeme zjisti aktuální pozici (offset) ukazatele v souboru, měřenou v bytech od začátku souboru. Pro posunutí tohoto ukazatele použijete metodu f.seek(), jejíž první argument bude specifikovat offset a druhý pozici vzhledem ke které se offset vztahuje. Jestliže pozici nespecifikujete (nebo použijete hodnotu 0), bude offset reprezentovat novou vzdálenost ukazatele od začátku souboru, hodnota 1 bude znamenat posun relativně k aktuální pozici (záporná hodnota posouvá zpět), konečně hodnota 2 posun o offset bytů od konce souboru.

>>> f=open('/tmp/workfile', 'r+')
>>> f.write('0123456789abcdef')
>>> f.seek(5)     # Jdi na šestý byte v souboru
>>> f.read(1)        
'5'
>>> f.seek(-3, 2) # Jdi na 3 byte před koncem souboru
>>> f.read(1)
'd'

Pokud jste skončili veškerou práci se souborem, je slušné ho uzavřít, čímž uvolníte systémové prostředky vyhrazené k manipulaci s tímto souborem. K uzavření souboru slouží metoda f.close(), po uzavření souboru již není možné k souboru jakýmkoli způsobem přistupovat:

>>> f.close()
>>> f.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: I/O operation on closed file

Další metody souborových objektů (f.isatty() nebo f.truncate()) se používají pouze zřídka a proto se jim zde nebudeme věnovat, případný zájemce najde veškeré informace v Python Library Reference.

 
7.2.2 Modul pickle

 

Jak jsme si ukázali, Python dokáže bez problémů zapsat do souboru řetězce. Jestliže však potřebujete číst nebo zapisovat čísla, neobejdete se bez dalšího kódu, který bude provádět případnou konverzi z čísla na řetězec a naopak. V případě, kdy budete chtít zapisovat komplexnější datové typy jako třeba seznamy, tuple, slovníky nebo dokonce instance uživatelských tříd, budete muset buď napsat hodně kódu nebo použít již připravený modul pickle.

Ten umí ukládat do souboru veškeré interní datové typy Pythonu, většinu instancí uživatelských tříd a dokonce i některé formy kódu. Pro implementaci tohoto zápisu používá převod na řetězcovou reprezentaci (tento proces se nazývá pickling nebo též serializace). Zároveň dokáže z řetězcové reprezentace zrekonstruovat původní objekt (unpickling, deserializace). Řetězcová reprezentace objektu obsahuje veškeré informace o jeho vnitřním stavu, můžete jí přenést po počítačové síti nebo uložit do souboru a přesto při deserializaci získáte zpět objekt s původním obsahem.

Předpokládejme, že chcete do souboru f uložit nějaký objekt x. Jednoduše tak učiníte funkcí pickle.dump():

import pickle
pickle.dump(x, f)

Pro rekonstrukci objektu ze souboru f stačí použít funkci pickle.load():

x = pickle.load(f)

Modul pickle podporuje mnohem více funkcí, než jsme si zde v krátkosti načrtli, kompletní přehled opět poskytne Python Library Reference.

Připomeňme, že modul pickle je standardní modul pro implementaci perzistentních objektů, tj. objektů, které mohou být uloženy a znovu použity jiným programem, "žijí" nezávisle na programu. A jelikož module pickle najdete v každé instalaci Pythonu, existuje silný tlak na tvůrce nových modulů, kteří musí chtě nechtě podporovat rozhraní tohoto modulu. Díky tomu je možné pomocí pickle ukládat i další datové typy (např. matice balíku NumPy apod.).



Footnotes

... souboru.7.1
Poznamenejme, že na Unixových systémech se stírá rozdíl mezi oběma přístupy, lze zde totiž přesměrovat standardní výstup do souboru. To je ovšem dáno filozofií tohoto výborného systému.
... výstup.7.2
Další možností je používat objekty reprezentující standardní výstup (resp. standardní chybový výstup) sys.stdout (sys.stderr). Pomocí jejich metody write() do nich můžete zapsat libovolný řetězec. Více informací o těchto standardních souborových objektech získáte z dokumentu Python Library Reference.
... řetězce:7.3
Poznamenejme, že úplnou tabulku kontrolních znaků a popis operátoru % najdete ve specifikaci jazyka Python.
...str().7.4
Oproti C však nejsou podporovány některé příkazy, jmenovitě %n a %p.
...open()7.5
V novějších verzích jazyka je preferována funkce file() se stejným rozhraním.
Viz O tomto dokumentu... kde naleznete informace, jak upozornit na případné chyby.