Níže uvedený text pochází z původního cztutfiles.html z prvního vydání. Ten byl rozdělen na rozšířený cztutfiles.html a nový soubor cztuttext.html. Nad tímto textem se nachází aktuální stav po revizi směřující k druhému vydání.
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.
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 učebnice 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é.
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.
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.
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
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
readlines()
jazyka Python přečte všechny řádky
souboru najednou, zatímco funkce readline()
přečte jen jeden
řádek. Může nám to pomoci šetřit pamětí.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: cztuttext.html,v 1.2 2005/09/03 22:31:43 petr Exp $