S binárními soubory se pracuje prakticky stejně jako se soubory textovými:
with open('obrazek.png', mode='rb') as f:
data = f.read()
Jen nemá žádný smysl uvádět kódování (protože se týká právě jen textových souborů) a také ukazatel aktuální pozice ve streamu tell() vždy souhlasí s počtem bajtů, které jsme už načetli.
Binární soubor (povinně identifikovaný příznakem b) můžeme otevřít v několika módech:
br – soubor je otevřen pouze pro čtení
bw – soubor je otevřen pro zápis (již existující neprázdný soubor bude smazán)
bx – soubor je exkluzivně vytvořen (tzn. že přístup selže s výjimkou FileExistsError, pokud soubor již existuje) a otevřen pro zápis
ba – soubor je otevřen pro přidávání (zapsaná data budou přidána na konec)
br+ – soubor je otevřen pro čtení i zápis (zapsaná data budou přidána na začátek)
bw+ – soubor je otevřen pro čtení i zápis (již existující neprázdný soubor bude smazán)
Souhrnně tedy:
Módy br a br+ očekávají již existující soubor.
Mód ba otevře soubor pro přidávání, tedy jako pro zápis, ale navíc posune ukazatel na konec.
Narozdíl od něj mód br+ sice také otevře soubor i pro přidávání, ale ukazatel ponechá na začátku.
Mějte zvláště na paměti následující rozdíly mezi binárními a textovými soubory:
Binární soubory jsou čteny (a zapisovány) po bajtech, textové jsou zpracovávány po znacích (přičemž jeden znak zabírá podle použitého kódování místo jednoho či několika bajtů).
V textových souborech je automaticky prováděna konverze konců řádků různých platforem (\n na Unixu a MacOS X+, \r\n na Windows, \r na MacOS 9-) na jednotné pracovní \n (konverze je samozřejmě obousměrná), v binárních pochopitelně nikoli.
U binárního souboru se narozdíl od textového dostává do popředí možnost posunout se na jeho libovolné místo (počítáno na bajty), aniž bychom při tom museli načítat data. K tomu slouží primárně dvě metody:
seek(offset[, 0|1|2]) – přesuň se na daný bajt v proudu; a to buď od začátku (SEEK_SET, 0 – výchozí hodnota), aktuální pozice (SEEK_CUR, 1) nebo konce (SEEK_END, 2)
tell() – vrať aktuální pozici ve streamu
# otevřme textový soubor (v kódování UTF-8) jako binární
>>> f = open('japonstina.txt', mode='br')
>>> f.tell() # s čertvě otevřeným souborem stojíme na začátku proudu
0
>>> f.read(2) # přečtěmež dva bajty..
b'\xe7\x8b'
>>> f.tell() # ..a posunuli jsme se také právě o dvě pozici dále
2
>>> f.seek(0) # vraťme se na začátek..
0
>>> f.tell() # ..a skutečně tam jsme
0
>>> f.close()
PS: Pro posun v proudu od konce jsou pro offset pochopitelně možné pouze záporné hodnoty (stejně jako od začátku zase pouze kladné).
Číst data z binárního proudu můžeme dvěma metodami. Hlavní odlišnost spočívá v jejich chování vůči ukazateli do proudu:
read([N]) – načti všechny nebo uvedený počet bajtů & posuň ukazatel do proudu
peek([N]) – načti blíže neurčený počet bajtů (číslo N zde slouží pouze jako „nápověda“) & neposouvej ukazatel do proudu
# otevřme textový soubor (v kódování UTF-8) jako binární
>>> f = open('japonstina.txt', mode='br')
>>> f.tell() # s čertvě otevřeným souborem stojíme na začátku proudu
0
>>> f.read(2) # přečtěmež dva bajty..
b'\xe7\x8b'
>>> f.tell() # ..a posunuli jsme se také právě o dvě pozici dále
2
>>> f.close()
Počet načtených bajtů metody peek() není (z hlediska programátora) předem jasný – metoda provede jedno čtení vstupního binárního proudu, jehož konkrétní provedení záleží na vnitřní implementaci práce s proudem. Ve výsledku se tak může vrátit bajtů i více nebo méně, než bylo vyžádáno:
# otevřme textový soubor (v kódování UTF-8) jako binární
>>> f = open('japonstina.txt', mode='br')
>>> f.tell() # s čertvě otevřeným souborem stojíme na začátku proudu
0
>>> f.peek(1) # zkusme se podívat na následující jeden bajt
b'\xe7\x8b\xbc.cz'
>>> f.tell() # ač jsme dostali nazpátek všechny, stojíme stále na začátku
0
>>> f.close()
Pro zápis bajtů na výstupní proud slouží primárně metoda write():
write(b) – zapiš uvedený byte-objekt b a vrať počet skutečně zapsaných bajtů
>japonstina.py
Zapsáno bajtů: 3
Do výstupního binárního souboru jsme zapsali tři bajty. K našemu štěstí (protože to víme :-) jsou to právě ty tři bajty, které dohromady tvoří při kódování UTF-8 japonský znak 狼 (tedy „ookami“), který pak uvidíme na výstupu (japonstina.out), pokud se na daný binární soubor podíváme jako na textový v příslušném kódování.
Při zápisu binárních dat platí stejná veledůležitá poznámka jako u dat textových:
Data jsou při zápisu ukládána nejdříve do mezipaměti (buffer), odkud nemusí být před vyžádaným uzavřením proudu skutečně zapsána.
Proto je důležité soubor po skončení práce uzavřít metodou close() (nebo ještě lépe pracovat se souborem rovnou uvnitř bloku with, který se o uzavření postará sám). Nebo si explicitně vyžádat zápis dat čekajících v mezipaměti metodou flush():
flush() – vynuť si uložení dat z mezipaměti (buffer) do proudu (streamu)
close() – uzavři otevřený proud (čímž se uloží veškerá případně dosud neuložená data)
# otevřme textový soubor (v kódování UTF-8) jako binární
>>> f = open('japonstina.txt', mode='br')
>>> f.tell() # s čertvě otevřeným souborem stojíme na začátku proudu
0
>>> f.read() # načtěmež celý proud – má 6 bajtů
b'\xe7\x8b\xbc.cz'
>>> f.tell() # jsme na jeho konci
6
>>> f.seek(0) # vraťme se na začátek
0
>>> f.tell()
0
>>> f.read(1) # přečtěmež jeden bajt..
b'\xe7'
>>> f.tell() # ..a posunuli jsme se také právě o jednu pozici dále
1
>>> f.close()
Všimněte si, že je-li soubor otevřen jako binární, načítají se z něj skutečně jednotlivé bajty – první tři jsou na konzoli vypsány v hexadecimální podobě, protože neodpovídají žádnému dobře definovanému tisknutelnému znaku (toto privilegium je totiž bez uvedení kódování vyhrazeno pouze spodní polovině ASCII-tabulky, a ještě ne celé). V případě textového souboru byly první tři bajty při kódování UTF-8 vyhodnoceny jako japonský znak 狼 (tedy „ookami“).