Nacházíte se zde: Domů ‣ Ponořme se do Pythonu 3 ‣
Úroveň obtížnosti: ♦♦♦♢♢
❝ A nine mile walk is no joke, especially in the rain. ❞
(Jít devět mil není žádná legrace, zvlášť v dešti. [krátký film])
— Harry Kemelman, The Nine Mile Walk
Než jsem začal instalovat první aplikaci, obsahovaly Windows na mém laptopu 38 493 souborů. Po instalaci Pythonu 3 k nim přibylo téměř 3000 dalších. Každý významnější operační systém považuje soubory za základ ukládání dat. Koncepce souborů je tak zakořeněná, že by představa jiné možnosti dělala většině lidí problémy. Obrazně řečeno, váš počítač se topí v souborech.
Než můžeme ze souboru číst, musíme jej otevřít. Otvírání souborů v Pythonu už nemohlo být jednodušší.
a_file = open('examples/chinese.txt', encoding='utf-8')
V Pythonu najdeme zabudovanou funkci open()
, která přebírá jméno souboru jako argument. Jménem souboru je zde 'examples/chinese.txt'
. Na uvedeném jméně souboru najdeme pět zajímavostí:
open()
požaduje jen jeden. Kdykoliv se po vás v Pythonu požaduje „jméno souboru“, můžete do něj zahrnout také celou adresářovou cestu nebo její část.
Ale volání funkce open()
nekončí zadáním jména souboru. Máme zde další argument nazvaný encoding
(kódování). No nazdar. To zní příšerně povědomě.
Bajty jsou bajty, znaky jsou abstrakce. Řetězec je posloupností znaků v Unicode. Ale soubor na disku není posloupností Unicode znaků. Soubor na disku je posloupností bajtů. Takže jak Python převádí posloupnost bajtů na posloupnost znaků, když čteme „textový soubor“ z disku? Dekóduje bajty podle určitého algoritmu pro kódování znaků a vrací posloupnost znaků v Unicode (známou také jako řetězec).
# Tento příklad byl vytvořen pod Windows. Z důvodů popsaných # níže se na ostatních platformách může chovat jinak. >>> file = open('examples/chinese.txt') >>> a_string = file.read() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "C:\Python31\lib\encodings\cp1252.py", line 23, in decode return codecs.charmap_decode(input,self.errors,decoding_table)[0] UnicodeDecodeError: 'charmap' codec can't decode byte 0x8f in position 28: character maps to <undefined> >>>
Co se to vlastně stalo? Neurčili jsme znakové kódování, takže Python byl donucen použít výchozí kódování. Co to je výchozí kódování? Pokud se pořádně podíváme na trasovací výpis, vidíme, že skončil uvnitř cp1252.py
. To znamená, že Python použil jako výchozí kódování CP-1252. (CP-1252 je běžné kódování, které se používá na počítačích s Microsoft Windows. To se týká západní Evropy. Čeština a slovenština používají kódování CP-1250.) Znaková sada CP-1252 nepodporuje znaky, které se v souboru nacházejí, takže čtení selhává s nepěknou chybou UnicodeDecodeError
.
Ale počkejte. Ono je to ještě horší! Výchozí kódování je závislé na platformě, takže stejný kód by na vašem počítači fungovat mohl (pokud by vaším výchozím kódováním bylo UTF-8). Ale pokud program přenesete k někomu jinému (kdo používá jiné výchozí kódování, jako třeba CP-1252), dojde k selhání.
☞Pokud potřebujete zjistit výchozí znakové kódování, importujte modul
locale
a zavolejtelocale.getpreferredencoding()
. Na mém laptopu s Windows funkce vrací'cp1252'
, ale na mém linuxovém stroji v horním pokoji se vrací'UTF8'
. Nejsem schopen udržet shodu dokonce ani ve svém vlastním domě! Ve vašem případě mohou být výsledky jiné (dokonce i pod Windows) v závislosti na verzi operačního systému, který jste nainstalovali, a na konfiguraci regionálních a jazykových nastavení. To je důvod, proč je tak důležité uvádět kódování pokaždé, když otvíráme soubor.
Zatím víme jen to, že Python má zabudovanou funkci zvanou open()
. Funkce open()
vrací objekt typu stream (čti [strím], proud dat), který poskytuje metody a atributy pro získávání informací o proudu znaků a pro manipulaci s ním.
>>> a_file = open('examples/chinese.txt', encoding='utf-8') >>> a_file.name ① 'examples/chinese.txt' >>> a_file.encoding ② 'utf-8' >>> a_file.mode ③ 'r'
name
zachycuje jméno, které jsme při otvírání souboru předali funkci open()
. Není upraveno do podoby absolutní cesty.
encoding
zachycuje kódování, které jsme při otvírání souboru předali funkci open()
. Pokud byste při otvírání souboru kódování neuvedli (nepořádný vývojář!), pak by atribut encoding
odpovídal výsledku locale.getpreferredencoding()
.
mode
poznáme, v jakém režimu byl soubor otevřen. Funkci open()
můžeme předat nepovinný parametr mode (režim). Při otvírání tohoto souboru jsme režim neurčili, takže Python použije výchozí hodnotu 'r'
, která má význam „otevřít jen pro čtení, v textovém režimu“. Jak uvidíme v této kapitole později, plní režim otevření souboru několik účelů. Různé režimy nám umožní do souboru zapisovat, připojovat na konec souboru nebo otvírat soubor v binárním režimu (ve kterém místo s řetězci pracujeme s bajty).
☞Seznam všech možných režimů najdete v dokumentaci pro funkci
open()
.
Po otevření souboru pro čtení z něj pravděpodobně v určitém místě budete chtít číst.
>>> a_file = open('examples/chinese.txt', encoding='utf-8') >>> a_file.read() ① 'Dive Into Python 是为有经验的程序员编写的一本 Python 书。\n' >>> a_file.read() ② ''
read()
objektu typu stream. Výsledkem je řetězec.
A co kdybychom chtěli soubor číst znovu?
# pokračování předchozího příkladu >>> a_file.read() ① '' >>> a_file.seek(0) ② 0 >>> a_file.read(16) ③ 'Dive Into Python' >>> a_file.read(1) ④ ' ' >>> a_file.read(1) '是' >>> a_file.tell() ⑤ 20
read()
vrací prázdný řetězec.
seek()
zajistí přesun v souboru na určenou bajtovou pozici.
read()
můžeme zadat nepovinný parametr, který určuje počet znaků, které se mají načíst.
Zkusme to znovu.
# pokračování předchozího příkladu >>> a_file.seek(17) ① 17 >>> a_file.read(1) ② '是' >>> a_file.tell() ③ 20
Už jste na to přišli? Metody seek()
a tell()
počítají vždy po bajtech, ale protože jsme soubor otevřeli v textovém režimu, čte metoda read()
po znacích. Pro zakódování čínských znaků v UTF-8 potřebujeme více bajtů. Pro každý anglický znak potřebujeme v souboru jen jeden bajt, takže by vás to mohlo svést k mylnému závěru, že metody seek()
a read()
počítají stejné jednotky. To ale platí jen pro některé znaky.
Ale moment, začíná to být ještě horší!
>>> a_file.seek(18) ① 18 >>> a_file.read(1) ② Traceback (most recent call last): File "<pyshell#12>", line 1, in <module> a_file.read(1) File "C:\Python31\lib\codecs.py", line 300, in decode (result, consumed) = self._buffer_decode(data, self.errors, final) UnicodeDecodeError: 'utf8' codec can't decode byte 0x98 in position 0: unexpected code byte
UnicodeDecodeError
.
Otevřené soubory zabírají systémové prostředky a v závislosti na režimu otevření souboru k nim některé programy nemusí být schopny přistupovat. Proto je důležité, abychom soubory zavírali hned poté, co s nimi přestaneme pracovat.
# pokračování předchozího příkladu >>> a_file.close()
Tak tohle bylo zklamání.
Objekt a_file typu stream pořád existuje. Volání jeho metody close()
nevede k jeho zrušení. Ale už není nějak zvlášť užitečný.
# pokračování předchozího příkladu >>> a_file.read() ① Traceback (most recent call last): File "<pyshell#24>", line 1, in <module> a_file.read() ValueError: I/O operation on closed file. >>> a_file.seek(0) ② Traceback (most recent call last): File "<pyshell#25>", line 1, in <module> a_file.seek(0) ValueError: I/O operation on closed file. >>> a_file.tell() ③ Traceback (most recent call last): File "<pyshell#26>", line 1, in <module> a_file.tell() ValueError: I/O operation on closed file. >>> a_file.close() ④ >>> a_file.closed ⑤ True
IOError
.
tell()
také selže.
close()
pro objekt typu stream, jehož soubor byl už zavřený, nevyvolá výjimku. Jde o prázdnou operaci.
closed
potvrzuje, že soubor byl uzavřen.
Objekty typu stream mají explicitní metodu close()
, ale co se stane, když je ve vašem programu chyba a zhavaruje předtím, než zavoláte close()
? Soubor by teoreticky mohl zůstat otevřený mnohem déle, než bychom potřebovali. Pokud zrovna něco ladíte na svém lokálním počítači, není to takový problém. Ale na používaném serveru už možná ano.
Python 2 pro tento případ nabízel řešení v podobě bloku try..finally
. V Pythonu 3 tento obrat stále funguje. Proto se s ním můžete setkat v kódu některých programátorů nebo ve starším kódu, který byl převeden pro Python 3. Ale Python 2.6 zavedl čistší řešení, které se v Pythonu 3 stalo preferovaným. Jde o příkaz with
.
with open('examples/chinese.txt', encoding='utf-8') as a_file:
a_file.seek(17)
a_character = a_file.read(1)
print(a_character)
V tomto kódu se volá open()
, ale nikde se v něm nevolá a_file.close()
. Příkaz with
zahajuje blok kódu podobně, jako je tomu u příkazu if
nebo u cyklu for
. Uvnitř bloku kódu můžeme používat proměnnou a_file, kterou objekt typu stream vrátil jako výsledek volání open()
. K dispozici máme všechny obvyklé metody objektu typu stream, jako jsou seek()
, read()
a všechny ostatní. Když blok with
skončí, Python automaticky zavolá a_file.close()
.
Když to shrneme, Python soubor uzavře nezávisle na tom, jak a kdy blok with
skončí… i kdyby „skončil“ v důsledku neošetřené výjimky. Tak to opravdu je. I v případě, kdy kód vyvolá výjimku a celý váš program se skřípěním zastaví, dotčený soubor bude uzavřen. Je to zaručeno.
☞Z technického pohledu příkaz
with
vytváří operační kontext (runtime context). Objekt typu stream je v těchto příkladech využit jako správce kontextu (context manager). Python vytvoří objekt a_file typu stream a řekne mu, že vstupuje do operačního kontextu. Jakmile blok příkazuwith
skončí, Python sdělí objektu typu stream, že opouští operační kontext a objekt zavolá svou vlastní metoduclose()
. Detaily hledejte v příloze B, „Třídy, které mohou být použity v blokuwith
“.
Příkaz with
není nijak zvlášť zaměřen na soubory. Je to prostě obecný rámec pro vytvoření operačního kontextu. Objekt se dozví, že vstupuje do operačního kontextu nebo že z něj vystupuje. Pokud je dotčený objekt typu stream, pak provede užitečné „souborové“ věci (jako je například automatické uzavření souboru). Ale toto chování je definováno uvnitř objektu typu stream a ne v příkazu with
. Správce kontextu může být použit mnoha jinými způsoby, které nemají se soubory nic společného. Můžete si dokonce vytvořit svého vlastního správce kontextu. Ukážeme si to o něco později, ale ještě v této kapitole.
„Řádek“ textového souboru je to, co si myslíte, že by to mělo být — napíšete pár slov, stisknete ENTER a najednou jste na novém řádku. Řádek textu je posloupnost znaků oddělená… čím vlastně? Ono je to komplikované, protože textové soubory mohou pro označení konce řádků použít několik různých znaků. Každý operační systém má svou vlastní konvenci. Některé používají znak návratu vozíku (carriage return), jiné používají znak přechodu na nový řádek (line feed) a některé používají na konci každého řádku oba zmíněné znaky.
Teď si můžete s úlevou oddechnout, protože Python zpracovává konce řádků automaticky. Pokud řeknete „chci přečíst tento textový soubor řádek po řádku“, Python zjistí, který typ konců řádků se v textovém souboru používá, a zařídí, že to prostě bude fungovat.
☞Pokud potřebujete získat detailní kontrolu nad tím, co se považuje za konec řádku, můžete funkci
open()
předat nepovinný parametrnewline
. Detaily najdete v dokumentaci funkceopen()
.
Takže jak se to vlastně dělá? Čtěte ze souboru po řádcích. Je to tak jednoduché. V jednoduchosti je krása.
line_number = 0
with open('examples/favorite-people.txt', encoding='utf-8') as a_file: ①
for a_line in a_file: ②
line_number += 1
print('{:>4} {}'.format(line_number, a_line.rstrip())) ③
with
dosáhneme bezpečného otevření souboru a necháme Python, aby ho zavřel za nás.
for
. To je vše. Objekty typu stream podporují metody jako read()
, ale kromě toho je objekt typu stream také iterátorem, který vrátí jeden řádek pokaždé, když jej požádáte o další hodnotu.
format()
. Specifikátor formátu {:>4}
říká „vytiskni tento argument zarovnaný doprava na šířku čtyř pozic“. Proměnná a_line obsahuje celý řádek, včetně znaků ukončujících řádek. Řetězcová metoda rstrip()
odstraní všechny koncové bílé znaky (whitespace) včetně znaků ukončujících řádek.
you@localhost:~/diveintopython3$ python3 examples/oneline.py 1 Dora 2 Ethan 3 Wesley 4 John 5 Anne 6 Mike 7 Chris 8 Sarah 9 Alex 10 Lizzie
Setkali jste se s následující chybou?
you@localhost:~/diveintopython3$ python3 examples/oneline.py Traceback (most recent call last): File "examples/oneline.py", line 4, in <module> print('{:>4} {}'.format(line_number, a_line.rstrip())) ValueError: zero length field name in formatPokud ano, pravděpodobně používáte Python 3.0. Měli byste provést aktualizaci na Python 3.1.
Python 3.0 sice podporuje nový způsob formátování řetězců, ale vyžaduje explicitní formátování specifikátorů formátu. Python 3.1 vám umožní ve specifikátorech formátu indexy argumentů vynechávat. Verze kompatibilní s Pythonem 3.0 je pro porovnání zde:
print('{0:>4} {1}'.format(line_number, a_line.rstrip()))
⁂
Do souborů můžeme zapisovat velmi podobným způsobem, jakým z nich čteme. Soubor nejdříve otevřeme a získáme objekt typu stream. Pro zápis do souboru použijeme jeho metody. Nakonec soubor zavřeme.
Při otvírání souboru pro zápis použijeme funkci open()
a předepíšeme režim zápisu. U souborů můžeme použít dva režimy zápisu:
open()
předáme mode='w'
.
open()
předáme mode='a'
.
Pokud soubor dosud neexistuje, bude při obou uvedených režimech vytvořen automaticky. To znamená, že se nikdy nemusíme piplat s funkčností jako „pokud soubor ještě neexistuje, vytvoř nový, prázdný soubor, abychom jej mohli poprvé otevřít“. Prostě soubor otevřeme a začneme zapisovat.
Jakmile zápis do souboru dokončíme, měli bychom jej vždy zavřít, aby došlo k uvolnění deskriptoru souboru (file handle) a abychom zajistili, že došlo ke skutečnému zápisu dat na disk. Stejně jako v případě čtení dat můžeme soubor zavřít voláním metody close()
objektu typu stream nebo můžeme použít příkaz with
a předat starost o zavření souboru Pythonu. Vsadím se, že uhodnete, kterou techniku doporučuji.
>>> with open('test.log', mode='w', encoding='utf-8') as a_file: ① … a_file.write('test succeeded') ② >>> with open('test.log', encoding='utf-8') as a_file: … print(a_file.read()) test succeeded >>> with open('test.log', mode='a', encoding='utf-8') as a_file: ③ … a_file.write('and again') >>> with open('test.log', encoding='utf-8') as a_file: … print(a_file.read()) test succeededand again ④
test.log
(nebo přepsáním existujícího souboru) a jeho otevřením pro zápis. Parametr mode='w'
znamená „otevři soubor pro zápis“. Ano, je to opravdu tak nebezpečné, jak to zní. Doufám, že vám na dřívějším obsahu tohoto souboru nezáleželo (pokud existoval), protože jeho obsah právě zmizel.
write()
objektu, který vrátila funkce open()
. Jakmile blok with
skončí, Python soubor automaticky uzavře.
mode='a'
, abychom místo přepsání souboru připojili data na jeho konec. Připsání na konec (append) nikdy nezničí existující obsah souboru.
test.log
. Všimněte si také, že nepřibyly žádné znaky pro návrat vozíku nebo pro odřádkování. Soubor je neobsahuje, protože jsme je do něj ani při jedné příležitosti explicitně nezapsali. Znak pro návrat vozíku (carriage return) můžeme zapsat jako '\r'
, znak pro odřádkování (line feed) můžeme zapsat '\n'
. Protože jsme nic z toho neudělali, skončilo vše, co jsme zapsali do souboru, na jediném řádku.
Všimli jste si parametru encoding
, který jsme při otvírání souboru pro zápis předávali funkci open()
? Je důležitý. Nikdy ho nevynechávejte! Jak jsme si ukázali na začátku kapitoly, soubory neobsahují řetězce. Soubory obsahují bajty. Z textového souboru můžeme číst „řetězce“ jen díky tomu, že jsme Pythonu řekli, jaké má při převodu proudu bajtů na řetězec použít kódování. Zápis textu do souboru představuje stejný problém, jen z opačné strany. Do souboru nemůžeme zapisovat znaky, protože znaky jsou abstraktní. Při zápisu do souboru musí Python vědět, jak má řetězce převádět na posloupnost bajtů. Jediný způsob, jak se ujistit, že se provede správný převod, spočívá v uvedení parametru encoding
při otvírání souboru pro zápis.
⁂
Všechny soubory neobsahují text. Některé mohou obsahovat obrázky mého psa.
>>> an_image = open('examples/beauregard.jpg', mode='rb') ① >>> an_image.mode ② 'rb' >>> an_image.name ③ 'examples/beauregard.jpg' >>> an_image.encoding ④ Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: '_io.BufferedReader' object has no attribute 'encoding'
mode
obsahuje znak'b'
.
mode
, který odpovídá stejnojmennému parametru předanému funkci open()
.
name
— stejně jako textové objekty typu stream.
encoding
. Dává to smysl, že? Čteme (nebo zapisujeme) bajty a ne řetězce. Python tedy nemusí dělat žádný převod. Z binárního souboru dostaneme přesně to, co jsme do něj vložili. Žádná konverze není nutná.
Už jsem řekl, že čteme bajty? Ano, je to tak.
# pokračování předchozího příkladu >>> an_image.tell() 0 >>> data = an_image.read(3) ① >>> data b'\xff\xd8\xff' >>> type(data) ② <class 'bytes'> >>> an_image.tell() ③ 3 >>> an_image.seek(0) 0 >>> data = an_image.read() >>> len(data) 3150
read()
jako argument počet bajtů, které se mají načíst, a ne počet znaků.
read()
, a pozičním indexem, který nám vrací metoda tell()
. Metoda read()
čte bajty a metody seek()
a tell()
sledují počet přečtených bajtů. U binárních souborů budou vždy v souladu.
⁂
Představte si, že píšete knihovnu a jedna z vašich knihovních funkcí má číst data ze souboru. Funkce by mohla jednoduše převzít jméno souboru v řetězcové podobě, otevřít soubor pro čtení, přečíst jeho obsah a před skončením funkce jej uzavřít. Ale takhle byste to dělat neměli. Místo toho by rozhraní vaší funkce (API) mělo přebírat libovolný objekt typu stream.
V nejjednodušším případě je objektem typu stream cokoliv, co má metodu read()
, která přebírá nepovinný parametr size (velikost) a vrací řetězec. Pokud je metoda read()
zavolána bez uvedení parametru size, měla by ze zdroje informací přečíst všechna zbývající data a vrátit je jako jednu hodnotu. Pokud je metoda zavolána s parametrem size, přečte ze zdroje požadované množství dat a vrátí je. Pokud je zavolána znovu, pokračuje od místa, kde se čtením přestala, a vrací další část dat.
Vypadá to, jako kdybychom používali objekt typu stream vzniklý otevřením skutečného souboru. Rozdíl je v tom, že se neomezujeme na skutečné soubory. Zdrojem informací, ze kterého „čteme“, může být cokoliv: webová stránka, řetězec v paměti nebo dokonce výstup z jiného programu. Pokud vaše funkce přebírá objekt typu stream a jednoduše volá jeho metodu read()
, můžete zpracovávat libovolný zdroj informací, který se tváří jako soubor, aniž byste museli pro každý druh vstupu psát různý kód.
>>> a_string = 'PapayaWhip is the new black.' >>> import io ① >>> a_file = io.StringIO(a_string) ② >>> a_file.read() ③ 'PapayaWhip is the new black.' >>> a_file.read() ④ '' >>> a_file.seek(0) ⑤ 0 >>> a_file.read(10) ⑥ 'PapayaWhip' >>> a_file.tell() 10 >>> a_file.seek(18) 18 >>> a_file.read() 'new black.'
io
definuje třídu StringIO
, kterou můžeme dosáhnout toho, aby se řetězec v paměti choval jako soubor.
io.StringIO()
a předáme jí řetězec, který chceme použít jako zdroj „souborových“ dat. Teď máme k dispozici objekt typu stream a můžeme s ním dělat všechny možné odpovídající věci.
read()
„přečteme“ celý „soubor“. V takovém případě objekt třídy StringIO
jednoduše vrátí původní řetězec.
read()
vrací prázdný řetězec — stejně jako u opravdového souboru.
seek()
objektu třídy StringIO
se můžeme explicitně nastavit na začátek řetězce — stejně jako při volání téže metody u opravdového souboru.
read()
předáme parametr size, můžeme číst po větších kouscích i z řetězce.
☞Třída
io.StringIO
vám umožní chovat se k řetězci jako k textovému souboru. Existuje také třídaio.BytesIO
, která vám umožní chovat se k poli bajtů jako k binárnímu souboru.
Pythonovská standardní knihovna obsahuje moduly, které podporují čtení a zápis komprimovaných souborů. Různých komprimačních schémat existuje celá řada. Mezi newindowsovskými systémy patří mezi dva nejpopulárnější gzip a bzip2. (Mohli jste se setkat také s archivy PKZIP a s archivy GNU Tar. V Pythonu najdete moduly i pro tyto dva.)
Modul gzip
nám umožní vytvořit objekt typu stream pro čtení a zápis souborů komprimovaných algoritmem gzip. Příslušný objekt podporuje metodu read()
(pokud jsme jej otevřeli pro čtení) nebo metodu write()
(pokud jsme jej otevřeli pro zápis). To znamená, že k přímému zápisu nebo čtení souborů komprimovaných algoritmem gzip můžeme použít metody, které jsme se už naučili používat s normálními soubory. Nemusíme vytvářet pomocné soubory k ukládání dekomprimovaných dat.
Jako bonus navíc podporuje modul gzip
i příkaz with
, takže uzavření komprimovaného souboru můžete ponechat na Pythonu.
you@localhost:~$ python3 >>> import gzip >>> with gzip.open('out.log.gz', mode='wb') as z_file: ① ... z_file.write('A nine mile walk is no joke, especially in the rain.'.encode('utf-8')) ... >>> exit() you@localhost:~$ ls -l out.log.gz ② -rw-r--r-- 1 mark mark 79 2009-07-19 14:29 out.log.gz you@localhost:~$ gunzip out.log.gz ③ you@localhost:~$ cat out.log ④ A nine mile walk is no joke, especially in the rain.
'b'
v argumentu mode
.)
gunzip
(vyslovuje se „dží anzip“) dekomprimuje daný soubor a ukládá jeho obsah do nového souboru se stejným jménem, ale bez přípony .gz
.
cat
zobrazuje obsah souboru. Soubor obsahuje řetězec, který jsme původně zapsali v pythonovském shellu přímo do komprimovaného souboru out.log.gz
.
Setkali jste se s následující chybou?
>>> with gzip.open('out.log.gz', mode='wb') as z_file: ... z_file.write('A nine mile walk is no joke, especially in the rain.'.encode('utf-8')) ... Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'GzipFile' object has no attribute '__exit__'Pokud ano, pravděpodobně používáte Python 3.0. Měli byste provést aktualizaci na Python 3.1.
V Pythonu 3.0 se sice modul
gzip
nacházel, ale nepodporoval použití objektů komprimovaných souborů jako správců kontextu. V Pythonu 3.1 byla přidána možnost používat objekty gzip souborů i v příkazuwith
.
⁂
Machři na práci přes příkazový řádek už koncept standardního vstupu, standardního výstupu a standardního chybového výstupu znají. Tato podkapitola je určena těm ostatním.
Standardní výstup a standardní chybový výstup (běžně se zkracují jako stdout
a stderr
) jsou roury (pipe), které jsou zabudovány do každého systému, který je odvozen od UNIXu. Platí to i pro Mac OS X a pro Linux. Pokud voláte funkci print()
, tištěný obsah je odeslán do roury stdout
. Pokud váš program zhavaruje a tiskne trasovací výpis, posílá jej do roury stderr
. Ve výchozím stavu jsou obě uvedené roury napojeny na terminálové okno, ve kterém pracujete. Když váš program něco tiskne, zobrazuje se jeho výstup ve vašem terminálovém okně. Když program zhavaruje, vidíte trasovací výpis také ve svém terminálovém okně. V grafickém pythonovském shellu jsou roury stdout
and stderr
přesměrovány do vašeho „interaktivního okna“.
>>> for i in range(3): ... print('PapayaWhip') ① PapayaWhip PapayaWhip PapayaWhip >>> import sys >>> for i in range(3): ... l = sys.stdout.write('is the') ② is theis theis the >>> for i in range(3): ... l = sys.stderr.write('new black') ③ new blacknew blacknew black
print()
volaná v cyklu. Tady nic překvapujícího nenajdeme.
stdout
je definován v modulu sys
a jde o objekt typu stream. Když zavoláme jeho metodu write()
, vytiskne každý řetězec, který jí předáme, a potom vrátí délku na výstupu. Funkce print
ve skutečnosti dělá právě tohle. Na konec každého tištěného řetězce přidá znak ukončující řádek a pak volá sys.stdout.write
.
sys.stdout
a sys.stderr
výstup do stejného místa: do pythonovského integrovaného vývojového prostředí (IDE, pokud v něm pracujeme) nebo do terminálového okna (pokud Python spouštíme z příkazového řádku). Standardní chybový výstup (stejně jako standardní výstup) přechod na nový řádek nepřidávají. Pokud chceme přejít na nový řádek, musíme zapsat příslušné znaky pro přechod na nový řádek.
sys.stdout
a sys.stderr
jsou objekty typu stream, ale dá se do nich pouze zapisovat. Pokus o volání jejich metody read()
vždy vyvolá výjimku IOError
.
>>> import sys >>> sys.stdout.read() Traceback (most recent call last): File "<stdin>", line 1, in <module> IOError: not readable
sys.stdout
a sys.stderr
jsou objekty typu stream, i když podporují pouze zápis. Ale nejsou konstantní. Jde o proměnné. To znamená, že do nich můžeme přiřadit novou hodnotu — nějaký jiný objekt typu stream — a přesměrovat jejich výstup.
import sys
class RedirectStdoutTo:
def __init__(self, out_new):
self.out_new = out_new
def __enter__(self):
self.out_old = sys.stdout
sys.stdout = self.out_new
def __exit__(self, *args):
sys.stdout = self.out_old
print('A')
with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file):
print('B')
print('C')
Podívejte se na tohle:
you@localhost:~/diveintopython3/examples$ python3 stdout.py A C you@localhost:~/diveintopython3/examples$ cat out.log B
Setkali jste se s následující chybou?
you@localhost:~/diveintopython3/examples$ python3 stdout.py File "stdout.py", line 15 with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file): ^ SyntaxError: invalid syntaxPokud ano, pravděpodobně používáte Python 3.0. Měli byste provést aktualizaci na Python 3.1.
Python 3.0 podporoval příkaz
with
, ale každý příkaz mohl používat jen jednoho správce kontextu. Python 3.1 umožňuje použít v jednom příkazuwith
více správců kontextu.
Podívejme se nejdříve na poslední část.
print('A')
with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file):
print('B')
print('C')
Tenhle příkaz with
je docela komplikovaný. Přepíšu ho do trochu srozumitelnější podoby.
with open('out.log', mode='w', encoding='utf-8') as a_file:
with RedirectStdoutTo(a_file):
print('B')
Z přepisu je vidět, že ve skutečnosti jde o dva příkazy with
, z nichž jeden je zanořen do druhého. „Vnější“ příkaz with
by nám měl být povědomý. Otvírá textový soubor zakódovaný v UTF-8 a pojmenovaný out.log
pro zápis a přiřazuje objekt typu stream do proměnné pojmenované a_file. Ale je tu ještě jedna zvláštnost.
with RedirectStdoutTo(a_file):
Kdepak je část as
? Příkaz with
ji ve skutečnosti nevyžaduje. Podobně, jako když voláte funkci a ignorujete její návratovou hodnotu, můžete použít i příkaz with
, který nepřiřazuje kontext příkazu with
do nějaké proměnné. V tomto případě nás zajímají pouze vedlejší efekty kontextu RedirectStdoutTo
.
A jaké jsou ty vedlejší efekty? Nahlédněme do třídy RedirectStdoutTo
. Tato třída je uživatelsky definovaným správcem kontextu. Roli správce kontextu může hrát každá funkce, která definuje speciální metody __enter__()
a __exit__()
.
class RedirectStdoutTo:
def __init__(self, out_new): ①
self.out_new = out_new
def __enter__(self): ②
self.out_old = sys.stdout
sys.stdout = self.out_new
def __exit__(self, *args): ③
sys.stdout = self.out_old
__init__()
se volá bezprostředně po vytvoření instance. Přebírá jeden parametr — objekt typu stream, který chceme po dobu životnosti kontextu používat jako standardní výstup. Metoda uloží odkaz na objekt typu stream do instanční proměnné, aby jej mohly později používat ostatní metody.
__enter__()
patří mezi speciální metody třídy. Python ji volá v okamžiku vstupu do kontextu (tj. na začátku příkazu with
). Metoda ukládá aktuální hodnotu sys.stdout
do self.out_old a poté přesměruje standardní výstup přiřazením self.out_new do sys.stdout.
__exit__()
je další speciální metodou třídy. Python ji volá při opouštění kontextu (tj. na konci příkazu with
). Metoda obnoví původní nasměrování standardního výstupu přiřazením uložené hodnoty self.out_old do sys.stdout.
Spojme to všechno dohromady:
print('A') ①
with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file): ②
print('B') ③
print('C') ④
with
přebírá čárkou oddělený seznam kontextů. Uvedený seznam se chová jako posloupnost vnořených bloků with
. První kontext v seznamu je chápán jako „vnější“ blok, poslední jako „vnitřní“ blok. První kontext otvírá soubor, druhý kontext přesměrovává sys.stdout
do objektu typu stream, který byl vytvořen v prvním kontextu.
print()
je provedena v kontextu vytvořeném příkazem with
, a proto nebude tisknout na obrazovku. Místo toho provede zápis do souboru out.log
.
with
skončil. Python každému správci kontextu oznámil, že má udělat to, co se má udělat při opouštění kontextu. Správci kontextu jsou uloženi v zásobníku (LIFO). Druhý kontext při ukončování změnil obsah sys.stdout
zpět na původní hodnotu a potom první kontext uzavřel soubor pojmenovaný out.log
. A protože bylo přesměrování standardního výstupu obnoveno na původní hodnotu, bude funkce print()
tisknout zase na obrazovku.
Přesměrování standardního chybového výstupu funguje naprosto stejně. Jen se místo sys.stdout
použije sys.stderr
.
⁂
io
module — standardní dokumentace
sys.stdout
and sys.stderr
— standardní dokumentace
© 2001–11 Mark Pilgrim