přihlásit 9416/831724

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_CONSTANT_ENCAPSED_STRING in /web/htdocs2/wraithcz/home/www/python/data/sessions/sessions1.php on line 2