Nacházíte se zde: Domů Ponořme se do Pythonu 3

Úroveň obtížnosti: ♦♦♦♢♢

Soubory

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

 

Ponořme se

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.

Čtení z textových souborů

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í:

  1. Není to pouhé jméno souboru. Je to kombinace adresářové cesty a jména souboru. Hypotetická funkce pro otvírání souboru by mohla požadovat dva argumenty — adresářovou cestu a jméno souboru. Ale funkce 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.
  2. Uvedená adresářová cesta používá normální lomítko, ale neupřesnil jsem, jaký operační systém používám. Windows používají pro oddělování podadresářů zpětná lomítka, zatímco Mac OS X a Linux používají obyčejná lomítka. Ale v Pythonu fungují obyčejná lomítka i pod Windows.
  3. Uvedená adresářová cesta nezačíná lomítkem nebo písmenem disku, takže ji nazýváme relativní cesta. Mohli byste se zeptat — relativní k čemu? Zachovejte klid.
  4. Je to řetězec. Všechny moderní operační systémy (dokonce i Windows!) ukládají jména souborů a adresářů v Unicode. Python 3 plně podporuje jména cest, která nemusí být výhradně v ASCII.
  5. A nemusí vést jen na váš lokální disk. Můžete mít připojený síťový disk. Daný „soubor“ může být fiktivní součástí zcela virtuálního souborového systému. Pokud jej váš počítač považuje za soubor a může k němu jako k souboru přistupovat, může jej Python otevřít také.

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ě.

Kódování znaků vystrkuje svou ošklivou hlavu.

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 zavolejte locale.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.

Objekty typu stream

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'
  1. Atribut name zachycuje jméno, které jsme při otvírání souboru předali funkci open(). Není upraveno do podoby absolutní cesty.
  2. Podobně atribut 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().
  3. Z atributu 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().

Čtení dat z textového souboru

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()                                            
''
  1. Jakmile soubor otevřeme (při zadání správného kódování), spočívá čtení z něj v prostém volání metody read() objektu typu stream. Výsledkem je řetězec.
  2. Trochu překvapující je možná to, že další čtení ze souboru nevyvolá výjimku. Python nepovažuje čtení za koncem souboru za chybu. Vrátí se jednoduše prázdný ř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
  1. Protože jsme ještě pořád na konci souboru, další volání metody read() vrací prázdný řetězec.
  2. Metoda seek() zajistí přesun v souboru na určenou bajtovou pozici.
  3. Metodě read() můžeme zadat nepovinný parametr, který určuje počet znaků, které se mají načíst.
  4. Pokud budeme chtít, můžeme číst klidně i po jednom znaku.
  5. 16 + 1 + 1 = … 20?

Zkusme to znovu.

# pokračování předchozího příkladu
>>> a_file.seek(17)                    
17
>>> a_file.read(1)                     
'是'
>>> a_file.tell()                      
20
  1. Přesuneme se na 17. bajt.
  2. Přečteme jeden znak.
  3. A najednou jsme na 20. bajtu.

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
  1. Přesuneme se na 18. bajt a zkusíme přečíst jeden znak.
  2. Proč to selhalo? Protože na 18. bajtu není znak. Nejbližší znak začíná na 17. bajtu (a zabírá tři bajty). Pokus o čtení znaku od středu jeho kódované posloupnosti vede k chybě UnicodeDecodeError.

Zavírání souborů

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
  1. Ze zavřeného objektu nemůžeme číst. Vyvolá se tím výjimka IOError.
  2. V zavřeném souboru nemůžeme ani přesunovat pozici (seek).
  3. U zavřeného souboru neexistuje žádná aktuální pozice, takže metoda tell() také selže.
  4. Překvapením možná je, že volání metody close() pro objekt typu stream, jehož soubor byl už zavřený, nevyvolá výjimku. Jde o prázdnou operaci.
  5. Zavřený objekt typu stream má přece jen jeden užitečný atribut. Atribut closed potvrzuje, že soubor byl uzavřen.

Automatické zavírání souborů

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říkazu with skončí, Python sdělí objektu typu stream, že opouští operační kontext a objekt zavolá svou vlastní metodu close(). Detaily hledejte v příloze B, „Třídy, které mohou být použity v bloku with.

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.

Čtení dat po řádcích

„Řá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ý parametr newline. Detaily najdete v dokumentaci funkce open().

Takže jak se to vlastně dělá? Čtěte ze souboru po řádcích. Je to tak jednoduché. V jednoduchosti je krása.

[stáhnnout oneline.py]

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()))          
  1. Použitím vzoru with dosáhneme bezpečného otevření souboru a necháme Python, aby ho zavřel za nás.
  2. Pro čtení souboru po řádcích využijeme cyklus 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.
  3. Číslo řádku a řádek samotný můžeme zobrazit s využitím řetězcové metody 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 format

Pokud 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()))

Zápis do textových souborů

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:

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                                           
  1. Začali jsme odvážně vytvořením nového souboru 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.
  2. Do nově otevřeného souboru můžeme data přidávat metodou write() objektu, který vrátila funkce open(). Jakmile blok with skončí, Python soubor automaticky uzavře.
  3. To bylo zábavné. Zkusme to znovu. Ale tentokrát použijeme 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.
  4. Jak původně zapsaný řádek, tak druhý řádek, který jsme připojili teď, se nacházejí v 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.

A znovu kódování znaků

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.

Binární soubory

Můj pes Beauregard.

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'
  1. Otevření souboru v binárním režimu je jednoduché, ale záludné. Ve srovnání s otvíráním v textovém režimu spočívá jediný rozdíl v tom, že parametr mode obsahuje znak'b'.
  2. Objekt typu stream, který získáme otevřením souboru v binárním režimu, má mnoho stejných atributů, včetně atributu mode, který odpovídá stejnojmennému parametru předanému funkci open().
  3. Binární objekty typu stream mají také atribut name — stejně jako textové objekty typu stream.
  4. Ale jeden rozdíl tady přesto je. Binární objekty typu stream nemají atribut 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
  1. Stejně jako v případě textových souborů také z binárních souborů můžeme číst po kouscích. Ale je tu jeden zásadní rozdíl…
  2. … čteme bajty, ne řetězce. Protože jsme soubor otevřeli v binárním režimu, přebírá metoda read() jako argument počet bajtů, které se mají načíst, a ne počet znaků.
  3. To znamená, že zde nikdy nedojde k neočekávanému nesouladu mezi číslem, které jsme předali metodě 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.

Objekty typu stream z nesouborových zdrojů

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.'
  1. Modul io definuje třídu StringIO, kterou můžeme dosáhnout toho, aby se řetězec v paměti choval jako soubor.
  2. Když chceme z řetězce vytvořit objekt typu stream, vytvoříme instanci třídy 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.
  3. Voláním metody read() „přečteme“ celý „soubor“. V takovém případě objekt třídy StringIO jednoduše vrátí původní řetězec.
  4. Opakované volání metody read() vrací prázdný řetězec — stejně jako u opravdového souboru.
  5. Použitím metody 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.
  6. Pokud metodě 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řída io.BytesIO, která vám umožní chovat se k poli bajtů jako k binárnímu souboru.

Práce s komprimovanými soubory

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.
  1. Soubory zabalené gzip bychom měli vždy otvírat v binárním režimu. (Všimněte si znaku 'b' v argumentu mode.)
  2. Tento příklad jsem vytvořil na Linuxu. Pokud vám tento příkazový řádek nic neříká, zobrazuje výpis položky souboru „v dlouhém formátu“ (v pracovním adresáři). Soubor jsme právě vytvořili v pythonovském shellu s využitím komprese gzip. Tento soubor ukazuje, že soubor existuje (fajn) a že má velikost 79 bajtů. Ve skutečnosti je větší než řetězec, se kterým jsme začali! Soubor ve formátu gzip zahrnuje hlavičku pevné délky, která obsahuje nějaké informace o souboru. Pro velmi malé soubory je to tedy neefektivní.
  3. Příkaz 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.
  4. Příkaz 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říkazu with.

Standardní vstup, výstup a chybový výstup

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
  1. Funkce print() volaná v cyklu. Tady nic překvapujícího nenajdeme.
  2. 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.
  3. V nejjednodušším případě posílají 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

Přesměrování standardního výstupu

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.

[stáhnout stdout.py]

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 syntax

Pokud 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říkazu with 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
  1. Metoda __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.
  2. Metoda __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.
  3. Metoda __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')                                                                             
  1. Výsledek se vytiskne v „interaktivním okně“ IDE (nebo v terminálovém okně, pokud skript spouštíme z příkazového řádku).
  2. Tento příkaz 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.
  3. Funkce 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.
  4. Blok kódu v příkazu 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.

Přečtěte si (vše anglicky)

© 2001–11 Mark Pilgrim