přihlásit 5307/589183

Kódování znaků v Pythonu
(rozpracováno)

Python má pro práci s textem dva datové typy str a unicode.

Datový typ unicode je řetěze, který pochopitelně používá kódování Unicode. Interní formát je ucs2 nebo ucs4 (záleží na nastavení při překladu), ale to nás prakticky nemusí zajímat, funkce na překódování se automaticky starají o správný převod.

Oproti tomu datový typ str je vlastně jen sekvence bajtů, v které může být cokoli. Dokonce i binární data včetně znaku s kódem 0 na rozdíl od jazyka C. Tudíž v str proměnné může být jakýkoli text v libovolném kódování. Programátor si musí sám někde udržovat informaci o tom, co to je a jaké to má kódování.

Viz následující ukázky v interaktivním interpretu, které probíhají v terminálu s utf-8 kódováním, takže zadaný text je v kódování utf-8.

Nejprve do proměnné text načteme z příkazového řádku (terminálu) nějaký text. V praxi může být jeho původ odkudkoli, načtený ze souboru, databáze, webu a podobně:

>>> text = raw_input()
ěščřžýáíé

Ověříme si datový typ proměnné:

>>> type(text)
<type 'str'>

Text můžeme na terminál zpátky vypsat:

>>> print text
ěščřžýáíé

A také se můžeme potívat na reprezentační řetězec, který nám proměnnou text zobrází jako sekvenci bajtů:

>>> text
'\xc4\x9b\xc5\xa1\xc4\x8d\xc5\x99\xc5\xbe\xc3\xbd\xc3\xa1\xc3\xad\xc3\xa9'

Vysvětlení pro ty, kteří tomuto výpisu nerozumí. Jedná se o řadu bajtových literálů. Bajtový literál je text '\xNN', kde NN je hodnota bajtu zapsaný v hexadecimální (šestnáctkové) soustavě. Příklad několika hodnot napsaných hexadecimální a poté decimální (desítkové) soustavě:

    00 = 0,    
    09 = 9,    
    0a = 10,   
    0f = 15, 
    10 = 16, 
    ff = 255

Na tomto výpisu si můžeme snadno spočítat, že náš text o devíti znacích v utf-8 zabírá 18 bajtů. To proto, že české znaky s diakritikou se v utf-8 zapisují pomocí dvou bajtů každý. Velikost si můžeme ověřit i takto:

>>> len(text)
18
Kdybychom chtěli vědět, ne kolik text zabírá bajtů, ale kolik má znaků, máme u datového typu str smůlu. Python neví v jakém kódování je text, a jak má jednotlivé bajty interpretovat. Pro Python je to prostě sekvence 18 bajtů, které nerozumí. Pokud bychom chtěli s textem pracovat opravdu jako s textem a ne sekvencí bajtů, pak text musíme převést na datový typ unicode. To můžeme udělat dvěma různými způsoby:
>>> utext1 = unicode(text, 'utf-8')
>>> utext2 = text.decode('utf-8')

Ještě si ověříme, zda jsou oba výsledky totožné.

>>> utext1 == utext2
True

Ano, jsou, proto každý může použít funkci, která je mu bližší a já už mohu dál pracovat jen s prvním výsledkem. Nejprve si ukážeme, že výsledek má opravdu datový typ unicode:

>>> type(utext1)
<type 'unicode'>

Díky tomu si můžeme nechat spočítat počet znaků textu, protože len() u unicode nevrací velikost textu v bajtech, ale v počtech znaků:

>>> len(utext1)
9

A pro kontrolu si unicode text i vypíšeme na terminál:

>>> print utext1
ěščřžýáíé

Možná se někdo správně diví, jak je možné, že vypisujeme unicode ve formátu ucs2 nebo ucs4 na terminál s utf-8 a všechno vypadá v pořádku. Je to díky tomu, že příkaz print je inteligentní, a při výpisu text automaticky zkonvertuje zpátky na utf-8. Jak pozná, že má použít zrovna tohle kódování nevím, ale i v českých windows na každý pád správně pozná, že má použít cp852.

Ale to je výjimka, protože například při zápisu do souboru, nebo i přímém zápisu do terminálu už žádné inteligentní rozpoznávání nepoužívá a o správné překódování se musíme postarat sami. Bez toho často dopadneme s touto chybou:

>>> import sys
>>> sys.stdout.write(utext1)
Traceback (most recent call last):
  File "", line 1, in ?
UnicodeEncodeError: 'ascii' codec can't encode characters in 
position 0-8: ordinal not in range(128)

Chybové hlášení říká, že došlo k chybě při překódování unicode textu, a že ascii kodek nemůže překódovat znaky na pozici 0 až 8. Když totiž text z unicode nepřekódujeme my, pak se o to Python pokusí sám. Jako cílové kódování použije výchozí kódování pythonu, které je přednastaveno na ASCII.

ASCII kódování je sedmibitové, obsahuje tedy jen 128 znaků. Jsou v něm písmena anglické abecedy, čísla, interpunkční znaménka a podobné základní znaky pro anglicky píšící lidi. Nejsou v něm žádné české znaky s diakritikou, kterých je náš pokusný text plný. To je problém, protože právě proto text s českými diakritickými znaky není možné na ASCII bezchybně převést a to způsobí v Pythonu vyvolání výjimky UnicodeEncodeError.

Řešení jsou dvě, změnit výchozí kódování, nebo provést konverzi ručně. To první je poněkud koplikované, to druhé se dá provést například do utf-8 takto:

>>> sys.stdout.write(utext1.encode('utf-8'))
ěščřžýáíé















Parse error: syntax error, unexpected T_STRING in /web/htdocs2/wraithcz/home/www/python/data/sessions/sessions1.php on line 2