Níže uvedený text pochází z prvního vydání. Nad tímto textem se nachází aktuální stav po revizi směřující k druhému vydání.

Práce se soubory a s textem

O čem si budeme povídat?

Zpracování souborů začátečníky často přivádí do úzkých, ačkoliv důvody jsou pro mne záhadou. Z pohledu programátora se soubory nijak neliší od souborů, které používáme při práci s textovým editorem nebo s jinou aplikací: musíme je otevřít, provedeme nějaké operace s obsahem a zase je zavřeme.

Největší rozdíl spočívá v tom, že v programu se k souboru přistupuje sekvenčně. To znamená, že čteme od jeho začátku, postupně po jednom řádku. Textový editor často dělá totéž, jenže si obsah celého souboru nejdříve načte do paměti, kde jej upravujete, a v okamžiku ukončení práce se souborem obsah paměti zapíše zpět do souboru a uzavře jej. (Proto se vám může zdát, že textový editor nepoužívá postupné čtení obsahu souboru.) Další rozdíl spočívá v tom, že z programu obvykle soubor otvíráme jen pro čtení nebo jen pro zápis. Při zápisu můžeme vytvořit zcela nový soubor (nebo můžeme přepisovat obsah již existujícího souboru) nebo obsah připojujeme na konec (append) existujícího souboru.

Další operace, kterou můžeme při zpracování souboru použít, je skok zpět na začátek.

Soubory — vstup a výstup

Podívejme se na to v praxi. Budeme předpokládat, že existuje soubor zvaný menu.txt a že obsahuje seznam jídel:

spam & vajíčka
spam & opékané brambory
spam & spam

Poznámka překladatele: Pojem spam jsme si již vysvětlovali. Pokud nečtete texty kurzu postupně, naleznete vysvětlení zde.

Nyní napíšeme program, který obsah souboru přečte a zobrazí jej na výstupu — podobně jako to v Unixu dělá příkaz cat nebo v DOSu příkaz type.

# Nejdříve soubor otevřeme ke čtení (r jako read).
vstup = open("menu.txt", "r")
# Poznámka překladatele: Od verze Python 2 by se 
# měla dávat přednost zápisu vstup = file("menu.txt", "r")

# Soubor načteme do seznamu řádků a pak
# každou položku seznamu (řádek) vytiskneme.
for radek in vstup.readlines():
    print radek
# A nyní soubor zase zavřeme.
vstup.close()

Poznámka 1: Operace open() vyžaduje dva argumenty. Prvním z nich je jméno souboru. Můžeme je předat prostřednictvím proměnné nebo je můžeme zapsat přímo jako řetězec, jako jsme to učinili zde. (Takovému zápisu řetězce se říká literál. Jde vlastně o přímo zapsanou řetězcovou konstantu.) Druhý argument určuje režim. Ten říká, zda soubor otvíráme pro čtení (r jako read) nebo pro zápis (w jako write). Můžeme též určit, zda se jedná o ASCII text nebo o binární data — přidáním 'b' za 'r' nebo za 'w' takto: open(jm_soub, "rb").

Poznámka 2: Ze souboru jsme četli a uzavírali jsme jej voláním funkcí, před které jsme připsali souborovou proměnnou. Tomuto zápisu se říká volání metody a je to naše první letmé setkání s objektovou orientací. Teď si s tím nelamte hlavu. Jenom si všimněte, že to má svým způsobem vztah k modulům. O použité souborové proměnné můžete uvažovat, jako kdyby to byla reference na modul, který obsahuje funkce pro práci se soubory a který se jakoby automaticky importuje pokaždé, když vytvoříme souborovou proměnnou.

Poznámka překladatele: Od verze jazyka Python 2 jeho autoři (Guido van Rossum a spol) upřednostňují objektový pohled na soubory. S tím souvisí i doporučení používat místo funkce open() zápis file(), který můžeme chápat jako vznik objektu souboru. Parametry mohou zůstat stejné. Vznikne tedy objekt odpovídající otevřenému souboru. Funkčně se tedy při náhradě volání open() zápisem file() nic nemění. Mění se ale způsob pohledu na problém.

A teď uvažme, jak bychom se mohli vypořádat s dlouhými soubory. V prvé řadě bychom místo použití readlines() museli soubor číst řádek po řádku — v jazyce Python k tomu slouží operace readline(). Mohli bychom použít proměnnou poc_radku, kterou bychom zvyšovali při načtení každého řádku a testovali bychom, jestli dosáhla hodnoty 25 (počet řádku na obrazovce). Pokud by tato situace nastala, požádáme uživatele, aby stiskl nějaké tlačítko (dejme tomu Enter). Potom bychom poc_radku nastavili na nulu a pokračovali bychom dál. Můžete si to vyzkoušet jako cvičení….

Ukázali jsme si skutečně všechno, co pro zpracování souboru potřebujeme. Otevřeme soubor, čteme z něj a manipulujeme s načtenými daty jak potřebujeme. Jakmile skončíme, soubor uzavřeme. Když budeme v jazyce Python chtít zapsat program pro příkaz copy, jednoduše otevřeme nový soubor pro zápis a místo tisku načtených řádků na displej je budeme zapisovat do tohoto souboru:

# Vytvoříme obdobu příkazu: COPY MENU.TXT MENU.BAK

# Nejdříve otevřeme soubory pro čtení (r) a pro zápis (w).
vstup = open("menu.txt", "r")
vystup = open("menu.bak", "w")

# Řádky vstupního souboru načteme do seznamu 
# a pak je okopírujeme do výstupního souboru.
for radek in vstup.readlines():
    vystup.write(radek)

print "1 soubor okopírován..."

# Nyní soubory zavřeme.
vstup.close()
vystup.close()

Všimli jste si, že jsem použil příkaz print k tomu, aby uživatel poznal, že se něco stalo? Podobná zpětná vazba pro uživatele je obvykle vhodná.

Na závěr byste mohli chtít, aby se načtená data přidávala na konec existujícího souboru. Jednou z možností by bylo otevřít výstupní soubor pro čtení, načíst jeho obsah do seznamu, připojit k seznamu data ze vstupního souboru a nakonec celý seznam zapsat jako novou verzi původního výstupního souboru. Pokud by byly soubory krátké, pak to nezpůsobí žádné problémy. Ale pokud je výstupní soubor velmi velký, třeba větší než 100MB, pak vám prostě při vytváření seznamu řádků dojde paměť. (I kdybyste měli dostatečně velkou paměť, takový postup by byl časově náročný.) Naštěstí můžeme operaci open() určit další režim "a" (jako append), který zajistí připojení dat na konec souboru — do souboru prostě zapisujeme. Je to dokonce ještě vylepšené tím, že pokud soubor neexistuje, bude vytvořen nový soubor — jako kdybyste použili režim "w".

Uveďme si příklad, kdy používáme takzvaný log[1] soubor, do kterého zapisujeme chybová hlášení. Přitom ale nechceme smazat předchozí záznamy, takže nové záznamy připisujeme na konec souboru (error = chyba; msg = message [mesidž] = zpráva):

def logError(msg):
   err = open("Errors.log", "a")
   err.write(msg)
   err.close()

V reálném světě bychom ovšem rádi nějakým způsobem omezili velikost souboru. Běžně se používá technika, kdy se jméno souboru odvodí z aktuálního data. Takže když se datum změní, vytvoří se automaticky nový soubor. Správce systému pak může snadno najít chyby, které se staly v určitý den. Může snadno rozhodnout, které soubory jsou staré, archivovat je a odstranit v případě, kdy už nebudou potřebné.

Počítání slov

Nyní se znovu podívejme na program pro počítání slov, o kterém jsem se zmínil v předchozí podkapitole. Připomeňme si, že pseudo kód vypadal takto:

def pocetSlov(s):
    seznam = split(s)  # seznam, kde prvkem je vždy slovo
    return len(seznam) # vrátíme počet prvků seznamu

for radek in soubor:
    celkem = celkem + pocetSlov(radek) # sečti počty za každý řádek
print "Soubor má %d slov." % celkem

Nyní již víme, jak lze načítat řádky souboru. Podívejme se blíže na tělo funkce pocetSlov(). Nejdříve chceme z daného řádku vytvořit seznam slov. Když se podíváme do referenční příručky jazyka Python na modul string, najdeme funkci nazvanou split (rozdělit na části, rozštípnout), která z řetězce vytvoří seznam částí oddělených mezerovými znaky (nebo jiným znakem, který můžeme určit). Nakonec znovu nahlédneme do dokumentace a zjistíme, že zabudovaná funkce len() vrací pro seznam počet v něm umístěných prvků. V našem případě to bude počet slov v řetězci, což je přesně to, co potřebujeme.

Takže konečná podoba zdrojového kódu vypadá takto:

import string
def pocetSlov(s):
    seznam = string.split(s)  # funkce split() se nachází v modulu string
    # Poznámka překladatele: Od verze Python 2 lze psát
    # seznam = s.split()
    return len(seznam) # vrátíme počet prvků seznamu

vstup = open("menu.txt", "r")
celkem = 0  # vytvoříme proměnnou a nastavíme jí počáteční hodnotu nula

for radek in vstup.readlines():
    celkem = celkem + pocetSlov(radek) # sečti počty za každý řádek
print "Soubor má %d slov." % celkem

vstup.close()

Tento program není tak docela správný, protože například započítá samostatně stojící znak '&' jako slovo (ačkoliv si vlastně můžete myslet, že je to správné). Program navíc zpracovává jediný soubor (menu.txt). Ale není příliš obtížné upravit jej tak, aby jméno souboru četl z příkazového řádku (argv[1]) nebo prostřednictvím raw_input(), jak jsme si ukázali v podkapitole Konverzace s uživatelem. Řešení ponechávám za domácí úkol.

BASIC a Tcl

Jazyky BASIC a Tcl poskytují svůj vlastní mechanismus práce se soubory. Od mechanismu jazyka Python se příliš neliší, takže si ukážeme, jak by se v nich dal napsat program cat. Tím se soubory v těchto jazycích skončíme.

Verze v jazyce BASIC

BASIC používá k identifikaci souborů koncept zvaný stream[2]. (Pro účely dalšího popisu použijme překlad datový kanál.) Tyto datové kanály se v jazyce BASIC označují čísly, což činí práci se soubory obtížnou. Pokud použijeme užitečnou funkci FREEFILE, která vrací číslo nejbližšího volného datového kanálu, můžeme se těmto problémům vyhnout. Pokud její výsledek uložíme do vhodně pojmenované proměnné, vyhnete se zmatkům typu který datový kanál/soubor má jaké číslo.

VSTUPSOUB = FREEFILE
OPEN "TEST.DAT" FOR INPUT AS VSTUPSOUB
REM Zkontrolujeme, zda nejsme na konci souboru (End Of File ... EOF)
REM a potom načteme řádek ze vstupu a vytiskneme jej.
WHILE NOT EOF(VSTUPSOUB)
    LINE INPUT #VSTUPSOUB, radek
    PRINT radek
WEND    
CLOSE #VSTUPSOUB

Verze v Tcl

Vzor způsobu zpracování obsahu souboru by teď měl být jasný. V jazyce Tcl by to vypadalo takto:

set vstupsoub [open "Test.dat" r]
while { [gets $vstupsoub radek] >= 0} {
     puts $radek
     }
close $vstupsoub

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 bude do subjektu dopisu automaticky vložena informace o jméně a verzi tohoto HTML dokumentu.

$Id: cztutfiles.html,v 1.2 2004/05/14 10:22:46 prikryl Exp $