associação pythonbrasil[11] django zope/plone planet Início Logado como (Entrar)

Diferenças para "TudoSobrePythoneUnicode"

Diferenças entre as versões de 6 e 8 (2 versões de distância)
Revisão 6e 2008-02-20 10:22:43
Tamanho: 10872
Editor: NiloMenezes
Comentário:
Revisão 8e 2008-02-22 14:56:06
Tamanho: 32977
Editor: NiloMenezes
Comentário:
Deleções são marcadas assim. Adições são marcadas assim.
Linha 123: Linha 123:
{{{ {{{#!python numbering=off
Linha 130: Linha 130:
{{{ {{{#!python numbering=off
Linha 135: Linha 135:
Você lembra que anteriormente eu disse que '''uni''' não possuía uma representação determinada no computador. Então, o que acontece se nós tentarmos imprimir '''uni''' ?

{{{#!python numbering=off
uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni
}}}

Você veria:

{{{
Traceback (most recent call last):
  File "t6.py", line 2, in ?
    print uni
UnicodeEncodeError: 'ascii' codec can't encode characters in position 1-4:
ordinal not in range(128)
}}}

O que aconteceu? Bem, você pediu ao Python para imprimir '''uni''', mas uma vez que '''uni''' não tem uma representação determinada no computador, Python tem que primeiro converter '''uni''' em alguma forma imprimível. Já que você não disse ao Python como fazer a conversão, ele assumiu que você queria ASCII. Infelizmente, ASCII só manipula valores entre 0 e 127, e uni contém valores fora da faixa, por isso você tem um erro.

Um método rápido de imprimir '''uni''' é usar o método do Python chamado repr():

{{{
uni = u"\u001A\u0BC3\u1451\U0001D10C"
print repr(uni)
}}}

Que imprime:

{{{
u'\x1a\u0bc3\u1451\U0001d10c'
}}}

Que, claro, faz sentido, uma fez que é exatamente como nós definimos '''uni'''. Mas '''repr(uni)''' é simplesmente inútil no mundo real assim como '''uni'''. O que nós realmente precisamos é aprender sobre codecs.
Linha 136: Linha 170:
'''Codecs'''
Em geral, os codecs da linguagem Python permitem transformações arbitrárias entre objetos. Entretanto, no contexto deste artigo, é suficiente entender codecs como funções que transformam objetos Unicode em strings Python em formato binário, e vice-versa.

'''Por que precisamos deles?'''
Objetos Unicode não possuem uma representação determinada no computador. Antes que um objeto Unicode possa ser impresso, armazenado em disco, ou enviado pela rede, ele deve ser codificado em um representação especifica. Isto é feito utilizando-se um codec. Alguns codecs populares que você pode ter ouvido falado no seu dia-a-dia: ascii, iso-8859-7, UTF-8, UTF-16.
Linha 137: Linha 177:
Para converter um valor Unicode em uma representação binária, você chama o método '''.encode''' com o nome do codec. Por exemplo, para converter um valor Unicode par UTF-8:

{{{
binary = uni.encode("utf-8")
}}}

O que você acha tornarmos '''uni''' mais interessante, adicionando alguns caracteres comuns:
{{{
uni = u"Hello\u001A\u0BC3\u1451\U0001D10CUnicode"
}}}

Agora, vamos observar como diferente codecs representam '''uni'''. Aqui um pequeno programa de teste:

'''test_codec01.py'''
{{{#!python numbering=off

if __name__ == '__main__':

    # Define nossa string Unicode
    uni = u"Hello\u001A\u0BC3\u1451\U0001D10CUnicode"

    # UTF-8 e UTF-16 podem codificar completamente qualquer string Unicode
    print "UTF-8", repr(uni.encode('utf-8'))
    print "UTF-16", repr(uni.encode('utf-16'))

    # ASCII pode apenas codificar valore entre 0-127. Abaixo, dizemos ao Python
    # para trocar caracteres que não podem ser codificados por '?'
    print "ASCII",uni.encode('ascii','replace')

    # ISO-8859-1 é similar ao ASCII
    print "ISO-8859-1",uni.encode('iso-8859-1','replace')

}}}

Que produz a seguinte saída:

{{{
UTF-8 'Hello\x1a\xe0\xaf\x83\xe1\x91\x91\xf0\x9d\x84\x8cUnicode'
UTF-16 '\xff\xfeH\x00e\x00l\x00l\x00o\x00\x1a\x00\xc3\x0bQ\x144
        \xd8\x0c\xddU\x00n\x00i\x00c\x00o\x00d\x00e\x00'
ASCII Hello????Unicode
ISO-8859-1 Hello????Unicode
}}}

Note que eu continuei a usar '''repr()''' para imprimir as strings UTF-8 e UTF-16. Por que? Bem, de outra forma os valores teriam sido impressos na tela utilizando seu conteúdo binário, o que seria difícil de mostrar neste documento.
Linha 138: Linha 224:

Digamos que alguém tenha dado a você um objeto Unicode codificado com UTF-8. Como converter de volta para Unicode? Você pode ingenuamente tentar isso:

|||||| A forma ingênua (e errada)||
|| uni = unicode( utf8_string )

Por que errada? Aqui temos um programa que faz exatamente isso:

{{{#!python numbering=off
uni = u"Hello\u001A\u0BC3\u1451\U0001D10CUnicode"
utf8_string = uni.encode('utf-8')

# Ingenuamente converte de volta para Unicode
uni = unicode(utf8_string)
}}}

Aqui o que acontece:
{{{
Traceback (most recent call last):
    File "t6.py", line 5, in ?
    uni = unicode(utf8_string)

    UnicodeDecodeError: 'ascii' codec can't decode byte 0xe0
    in position 6: ordinal not in range(128)
}}}

Você sabe, a função '''unicode()''' possui realmente dois parâmetros:
{{{#!python numbering=off
def unicode(string, encoding):
     ....
}}}

No exemplo acima, nós omitimos a codificação (encoding) logo o Python, na melhor das intenções, assumiu mais uma vez que nós queríamos ASCII (nota 1), e nos deu a coisa errada.

Aqui está a forma correta de fazer isso:

{{{#!python numbering=off
uni = u"Hello\u001A\u0BC3\u1451\U0001D10CUnicode"
utf8_string = uni.encode('utf-8')

# Tem que decodificar usando o mesmo codificador usado na codificação!
uni = unicode(utf8_string,'utf-8')
print "De volta para UTF-8: ",repr(uni)
}}}

Que resulta em:
{{{
De volta para UTF-8: u'Hello\x1a\u0bc3\u1451\U0001d10cUnicode'
}}}

Linha 139: Linha 276:
==== Uma pegadinha do \U ==== Os exemplos acima devem ter dado uma boa idéia do por quê você quer evitar tratar valores Unicode em formato binário o máximo possível! A versão UTF-8 tinha 23 bytes, a versão UTF-16 tinha 36 bytes, a versão ASCII tinha 16 bytes (mas ela descartou completamente 4 valores Unicode) e de forma similar com ISO-8859-1.

Isto é o por quê, desde o começo, sugeri que você esquecesse sobre bytes!

A boa nova é que uma vez que você tem um objeto Unicode, ele se comporta exatemente como um objeto string comum, logo não há sintaxe adicional a aprender (outra que os códigos de escape '''\u''' e '''\U'''). Aqui temos um pequeno exemplo que mostra objetos Unicode se comportando do jeito que você espera:

'''test_strings01.py'''
{{{#!python
if __name__ == '__main__':

    uni = u"Hello\u001A\u0BC3\u1451\U0001D10CUnicode"

    print "uni = ",repr(uni)

    print "len(uni) = ",len(uni)

    # print the "Hello" part
    print "uni[:5] = ",uni[:5]

    # print the Unicode characters one at a time
    print "uni[5] = ",repr(uni[5])
    print "uni[6] = ",repr(uni[6])
    print "uni[7] = ",repr(uni[7])

    # Depending on how Python was compiled, \U characters
    # may be stored as two Unicode characters -- see the
    # section "A wrinkle in \U" below for more details ...
    print "uni[8] = ",repr(uni[8])
    print "uni[9] = ",repr(uni[9])

    # print the "Unicode" text at the end
    print "uni[10:] = ",repr(uni[10:])
}}}

Executando este exemplo, obtemos o seguinte resultado:

{{{
uni = u'Hello\x1a\u0bc3\u1451\U0001d10cUnicode'
len(uni) = 17
uni[:5] = Hello
uni[5] = u'\x1a'
uni[6] = u'\u0bc3'
uni[7] = u'\u1451'
uni[8] = u'\ud834'
uni[9] = u'\udd0c'
uni[10:] = u'Unicode'
}}}

==== O pau do \U ====
Dependendo de como seu interpretador Python foi compilado, ele armazena objetos Unicode internamente em UTF-16 (2 bytes por caracter) ou UTF-32 (4 bytes por caracter). Infelizmente este nível de detalhe é exposto na interface normal de string.

Para caracteres de 4 dígitos(16 bits) como '''\u03a0''', não há diferença.

{{{#!python numbering=off
a = u'\u03a0'
print len(a)
}}}
Mostrará tamanho 1, não importa como o seu Python foi compilado, e {{{a[0]}}} será sempre {{{\u03a0}}}. Entretanto, para caracteres de 8 dígitos (32 bits), como {{{\U0001FF00}}}, você verá uma diferença. Obviamente, valores de 32 bits não podem ser diretamente representados em código de 16 bits, logo um par de valores de 16 bits é usado. (Códigos '''0xD800 - 0xDFFF'''), chamdos "surrogate pairs", são reservados para estas sequências de dois caracteres. Estes valores são invalidos se utilizados separadamente pela especificação Unicode.)

A programa exemplo que mostra o que acontece:

O que acontece com \U...
{{{#!python numbering=off
a = u'\U0001ff00'
print "Length:",len(a)

print "Chars:"
for c in a:
    print repr(c)
}}}

Se você executar este exemplo em um Python UTF-16, você verá:

Resultado, Python UTF-16
{{{
Length: 2
Chars:
u'\ud83f'
u'\udf00'
}}}

Em um Python UTF-32, você verá:

Resultado com Python UTF-32:
{{{
Length: 1
Chars:
u'\U0001ff00'
}}}

Este é um detalhe irritante de se preocupar. Eu escrevi um módulo que deixa você passar caracter a caracter dentro de uma string Unicode, independete de você utilizar Python UTF-16 ou Python UTF-32. Ele é chamado '''xmlmap''' e é parte dos [http://freshmeat.net/projects/gnosisxml/ utilitários Gnosis]. Aqui estão dois exemplos, um usando o xmlmap e outro não.

'''Sem xmlmap'''
{{{#!python numbering=off
a = u'A\U0001ff00C\U0001fafbD'
print "Length:",len(a)

print "Chars:"
for c in a:
    print repr(c)
}}}

'''Resultados sem o xmlmap, em um Python UTF-16'''
{{{
Length: 7
Chars:
u'A'
u'\ud83f'
u'\udf00'
u'C'
u'\ud83e'
u'\udefb'
u'D'
}}}

Agora, usando a função '''usplit()''' para conseguir os caracteres um por um, combinando valores quando necessário:

'''Com xmlmap'''
{{{#!python numbering=off
from gnosis.xml.xmlmap import usplit

a = u'A\U0001ff00C\U0001fafbD'
print "Length:",len(a)

print "Chars:"
for c in usplit(a):
    print repr(c)
}}}

'''Resultados com xmlmap, em um Python UTF-16'''
{{{
Length: 7
Chars:
u'A'
u'\U0001ff00'
u'C'
u'\U0001fafb'
u'D'
}}}

Agora você terá resultados idênticos, independente de como seu interpretador Python foi compilado. (Note que o tamanho continua o mesmo, mas '''usplit()''' combinou os "surrogate pairs" de forma que você não os vê aqui.)
Linha 141: Linha 419:

Você pode pensar quem liga quando isso vem do Python 2.0 e 2.1, mas ao escrevermos código supostamente portátil, isso importa!

O Python 2.0.x e 2.1.x tem um erro fatal quando tenta manipular códigos de um caracter na faixa '''\uD800-\uDFFF'''.

O exemplo abaixo apresenta o problema:

{{{#!python numbering=off
 u = unichr(0xd800)
 print "Orig: ",repr(u)

 # Cria utf-8 a partir de '\ud800'
 ue = u.encode('utf-8')
 print "UTF-8: ",repr(ue)

 # Decodifica de volta para Unicode
 uu = unicode(ue,'utf-8')
 print "Back: ",repr(uu)
}}}

Rodando isso no Python 2.2 e versões superiores produz o resultado esperado:

{{{
 Orig: u'\ud800'
 UTF-8: '\xed\xa0\x80'
 Back: u'\ud800'
}}}

Python 2.0.x retorna:
{{{
 Orig: u'\uD800'
 UTF-8: '\240\200'
 Traceback (most recent call last):
   File "test_utf8_bug.py", line 9, in ?
     uu = unicode(ue,'utf-8')
 UnicodeError: UTF-8 decoding error: unexpected code byte
}}}


Python 2.1.x retorna:
{{{
 Orig: u'\ud800'
 UTF-8: '\xa0\x80'
 Traceback (most recent call last):
   File "test_utf8_bug.py", line 9, in ?
     uu = unicode(ue,'utf-8')
 UnicodeError: UTF-8 decoding error: unexpected code byte
}}}

Como voocê pode ver, ambos falharam ao codificar '''\ud800''' quando usado como um caracter único. Ainda que seja verdade que caracteres entre 0xD800 .. 0xDFF não sejam válidos quando usados sozinhos, o fato é que Python lhe deixa usá-los.

=== Mas se são inválidos, por que o Python se importa? ===

Eu arranjei um bom exemplo, completamente por acidente enquanto trabalhava no código deste tutorial. Crie dois arquivos em Python:

'''aaa.py'''
{{{#!python numbering=off
x = u'\ud800'
}}}

'''bbb.py'''
{{{#!python numbering=off
import sys
sys.path.insert(0,'.')
import aaa
}}}

Agora, use o Python 2.0.x/2.1.x para executar '''bbb.py''' duas vezes (precisa executar duas vezes para que ele carregue aaa.pyc na segunda vez). Na segunda execução, você terá:

{{{
Traceback (most recent call last):
    File "bbb.py", line 3, in ?
      import aaa
  UnicodeError: UTF-8 decoding error: unexpected code byte
}}}

É isso mesmo: o Python 2.0.x/2.1.x não é capaz de recarregar seu próprio bytecode de um arquivo .pyc se o fonte contém uma string como '''\u0d800'''. Uma forma de resolver este problema seria usar '''unichr(0xd800)''' no lugar de '''\ud800''' (e é isto que o gnosis.xml.pickle faz).
Linha 143: Linha 499:
Até este ponto, eu converti Unicode de e para UTF para fins de demonstração. Entretanto, Python lhe permite fazer muito mais que isso. Python permite que você converta praticamente qualquer string multibyte em Unicode (e vice-versa). Implementando todas estas conversões dá um monte de trabalho. Felizmente, já foi feito, tudo que temos que fazer é usar.

Vamos revisitar nossa tabela grega, mas desta vez eu vou apresentar os caracteres tanto em Unicode quanto em ISO-8859-7 ("grego nativo").

||<style="background-color: #E0E0FF;">Caracter||<style="background-color: #E0E0FF;">Nome||<style="background-color: #E0E0FF;">Unicode||<style="background-color: #E0E0FF;">ISO-8859-7||
||Π||Greek Capital Letter Pi (Grego Letra Maiúscula Pi)||03A0||0xD0||
||Σ||Greek Capital Letter Sigma (Grego Letra Maiúscula Sigma)||03A3||0xD3||
||Ω||Greek Capital Letter Omega (Grego Letra Maiúscula Omega)||03A9||0xD9||

Com Python, usando '''unicode()''' e '''.encode()''' é trivial converter entre eles.

{{{#!python numbering=off
# {Pi}{Sigma}{Omega} como uma string codificada com ISO-8859-7
b = '\xd0\xd3\xd9'

# Converte para Unicode ('formato universal')
u = unicode(b, 'iso-8859-7')
print repr(u)

# ... e de volta para ISO-8859-7
c = u.encode('iso-8859-7')
print repr(c)
}}}

Mostra:
{{{
u'\u03a0\u03a3\u03a9'
\xd0\xd3\xd9
}}}

Você também pode usar Python como um "recodificador universal". Digamos que você tenha recebido um arquivo em japonês, usando a codificação [http://www.rikai.com/library/kanjitables/kanji_codes.sjis.shtml ShiftJIS] e queira convertê-lo para a codificação [http://www.rikai.com/library/kanjitables/kanji_codes.euc.shtml EUC-JP]:

{{{#!python numbering=off
txt = ... texto codificado com ShiftJIS ...

# converte para Unicode ("formato universal")
u = unicode(txt, 'shiftjis')

# converte para EUC-JP
out = u.encode('eucjp')
}}}

Claro que isto só funciona quando convertemos entre conjuntos de caracteres compatíveis. Tentar converter entre conjuntos de caracteres japoneses e gregos desta forma não funcionaria.
Linha 144: Linha 544:
Agora você já sabe tudo que precisa para trabalhar com objetos Unicode em Python. E isso é muito legal, não? Entretanto, o resto do mundo não é tão bacana quanto Python, você precisa saber como a porção do mundo não-Python suporta Unicode. Não é tão difícil, mas há muitos casos especiais a considerar.

De agora em diante, nós estaremos lidando com problemas que surgem quando trabalhamos com Unicode e:

 1. Nomes de arquivos (problemas específicos de sistemas operacionais)
 1. XML
 1. HTML
 1. Compartilhamento de rede (Samba)
Linha 146: Linha 554:
Parece simples, certo? Se você quiser nomear um arquivo com minhas letras gregas, eu diria:

{{{
open(unicode_name, 'w')
}}}

Teoricamente, sil, isto é supostamente tudo que deveríamos fazer. Mas, há muitas formas disto não funcionar, e elas dependem da plataforma que seu programa está rodando.
Linha 147: Linha 563:
Existem pelo menos duas formas de rodar Python em Windows. A primeira delas é usar os arquivos compilados para Win32 da [http://www.python.org/ www.python.org]. Eu vou me referir a este método como "Python nativo-Windows".

O outro método é usar a versão do Python que vem com o [http://www.cygwin.org/ Cygwin]. Esta versão do Python se parece mais (para o código do usuário) com POSIX do que um ambiente nativo Windows.

Para muitas coisas, as duas versões são compatíveis. Uma vez que se você escreve código Python multiplataforma, não deveria se preocupar com que interpretador você estará rodando. Porém, uma importante exceção é o suporte Unicode. Isto é o por quê de eu ser específico aqui sobre qual versão eu estou rodando.

==== Usando Python nativo-Windows ====
Vamos continuar a usar nossa tabela se símbolos gregos:

||||||<style="background-color: #E0E0FF;">Exemplos de Símbolos Unicode ||
||03A0||Π||Greek Capital Letter Pi (Grego Letra Maiúscula Pi)||
||03A3||Σ||Greek Capital Letter Sigma (Grego Letra Maiúscula Sigma)||
||03A9||Ω||Greek Capital Letter Omega (Grego Letra Maiúscula Omega)||

Nosso nome de arquivo Unicode exemplo será:
{{{#!python numbering=off
# this is: abc_{PI}{Sigma}{Omega}.txt
uname = u"abc_\u03A0\u03A3\u03A9.txt"
}}}

Vamos criar um arquivo com este nome, contendo uma única linha de texto:
{{{#!python numbering=off
open(uname,'w').write('Hello world!\n')
}}}

Abrir uma janela do Explorer mostra os resultados (clique na imagem para uma versão maior):

[http://boodebr.org/python/pyunicode/win32_01.png http://boodebr.org/python/pyunicode/win32_01.jpg]

Lá está o nome do arquivo com toda sua glória Unicode.

Agora, vamos ver como {{{os.listdir()}}} funciona com este nome. A primeira coisa a saber é que {{{os.listdir()}}} tem dois modos de operação:
 * Não-unicode, obtido ao se passar uma string não Unicode para {{{os.listdir()}}}, exemplo: {{{os.listdir('.')}}}
 * Unicode, obtido ao se passar uma string Unicode para {{{os.listdir()}}}, exemplo: {{{os.listdir(u'.')}}}

Primeiro, vamos tentar como Unicode:
{{{#!python numbering=off
os.chdir('ttt')
# Há apenas um arquivo no diretório 'ttt'
name = os.listdir(u'.')[0]
print "Nome: ",repr(name)
print "Linha: ",open(name,'r').read()
}}}

Executando-se este programa temos o seguinte resultado:
{{{
Nome: u'abc_\u03a0\u03a3\u03a9.txt'
Linha: Hello world!
}}}

Comparando acima, parece correto. Observe que {{{print repr(name)}}} foi requerido, uma vez que teria ocorrido um erro caso eu tentasse imprimir o nome diretamente na tela. Por que? Ora, mais uma vez o Python teria assumido que você queria uma codificação ASCII, e teria então falhado com um erro.

Agora vamos tentar o exemplo acima novamente, mas usando a versão não-Unicode de {{{os.listdir()}}}:

{{{#!python numbering=off
os.chdir('ttt')
# Há apenas um arquivo no diretório 'ttt'
name = os.listdir('.')[0]
print "Nome: ",repr(name)
print "Linha: ",open(name,'r').read()
}}}

Resulta na seguinte saída:

{{{
Nome: 'abc_?SO.txt'
Linha:
Traceback (most recent call last):
  File "c:\frank\src\unicode\t2.py", line 8, in ?
    print "Line: ",open(name,'r').read()
IOError: [Errno 2] No such file or directory: 'abc_?SO.txt'
}}}

Credo! O que aconteceu? Bem vindo ao maravilhoso mundo "API-dupla" win32.

Um pouco sobre isso:
 O Windows NT/2000/XP sempre escreve nomes de arquivo usando Unicode no sistema de arquivos (nota 2). Então, em teoria, nomes de arquivo Unicode deveriam funcionar perfeitamente em Python.
 Infelizmente, a win32 fornece duas APIs para fazer a interface com o sistema de arquivos. E usando o legítimo estilo Microsoft, elas são incompatíveis. As duas APIs são:
  1. Uma APIs para aplicações que suportam Unicode, que retornam nomes Unicode de verdade.
  1. Uma API para aplicações que não suportam Unicode que retornam uma representação dos nomes de arquivo Unicode usando uma codificação dependente do [http://en.wikipedia.org/wiki/Locale local].
  O Python (para melhor ou pior) segue estas convenções na plataforma win32, então você acaba ficando com duas formas incompatíveis de chamar {{{os.listdir()}}} e {{{open()}}}:
  1. Quando você chama {{{os.listdir()}}},{{{open()}}}, etc. com uma string Unicode, o Python chama a versão Unicode da API e você recebe os nomes de arquivo em Unicode de verdade. (Isto corresponde a primeira API que falamos acima).
  1. Quando você chama {{{os.listdir()}}},{{{open()}}}, etc. com uma string não-Unicode, o Python chama a versão não-Unicode da API, e é aqui que mora o problema. A API não-Unicode suporta Unicode com um codec particular chamado MBCS. MBCS é um codec que perde informação no processo: todo nome MBCS pode ser representado como Unicode, mas o contrário não é válido. A codificação MBCS também muda dependendo das configurações de local da máquina. Em outras palavras, se eu escrever um CD com um arquivo cujo nome possua caracteres multibytes em MBCS na minha máquina configurada para Inglês, e depois enviar o CD para o Japão, o nome do arquivo pode aparecer com caracteres completamente diferentes.

Agora que falamos do que há por trás disso, nós podemos entender o que aconteceu acima. Ao usarmos {{{os.listdir('.'}}}, você estará recebendo a versão MBCS do nome Unicode que está armazenado no sistema de arquivos. E, na minha máquina configurada para Inglês, não existe um mapeamento preciso para os caracteres gregos, e você acaba com "?", "S" e "O". Isto nos leva ao segundo resultado estranho que é não haver jeito de abrir nosso arquivo com letras gregas usando a API MBCS em uma máquina configurada com os parâmetros de localização em inglês(!!).

'''Fundo do poço'''
Eu recomendo sempre usar strings Unicode em {{{os.listdir()}}}, open(), etc. Lembre-se que o Windows NT/2000/XP sempre armazena os nomes de arquivo como Unicode, e esse é o comportamento padrão. E, como mostrado acima, pode algumas vezes ser a única forma de abrir um arquivo com nome em Unicode.
  
||<rowstyle="text-align: center;background-color: #FF1010">Perigo ! Cygwin||
|| O Cygwin tem um grande problema aqui. Ele (pelo menos por enquanto) não tem suporte a Unicode. Isto é, ele nunca irá chamar as versões Unicode da API win32. Assim, é impossível abrir certos arquivos (como o nosso arquivo com caracteres gregos no nome) no Cygwin. Não importa se você usa {{{os.listdir(u'.')}}} ou {{{os.listdir('.')}}}; você sempre receberá a versão codificada em MBCS. ||
|| Note também que isto não é um problema específico do Python; é um problema sistemático com Cygwin. Todos os utilitários Cygwin como zsh, ls, zip, unzip, mkisofs, não serão capazes de reconhecer um nome de arquivo com letras gregas, e irão reportar diversos errors. ||

Tudo Sobre Python e Unicode

Tradução de [http://boodebr.org/main/python/all-about-python-and-unicode "All about Python and Unicode"] por Nilo Menezes.

TableOfContents

Um ponto de partida

Duas semanas antes de começar a escrever este documento, meu conhecimento sobre [http://www.python.org/ Python] e [http://www.unicode.org/ Unicode] era algo como:

  • Tudo que precisa para usar Unicode em Python é passar suas strings para unicode()

Agora, onde eu fui arranjar uma idéia tão estranha? Ah, certo, do [http://docs.python.org/tut/node5.html#SECTION005130000000000000000 tutorial de Python sobre Unicode], que afirma:

  • "Criar strings Unicode em Python é tão simples quanto criar strings normais":

    >>> u'Alô Mundo !' u'Alô Mundo !'

Ainda que este exemplo seja tecnicamente correto, ele pode enganar o iniciante em Unicode, uma vez que ele esconde diversos detalhes necessários para o uso prático. Esta explicação ultra simplificada me deu um entendimento completamente errado sobre como Unicode funciona em Python.

Se você também foi guiado pelo caminho ultra simplificado, então este tutorial irá provavelmente ajudá-lo. Este tutorial contém um conjunto de exemplos, testes e demonstrações que documentam meu "reaprendizado" da forma correta de trabalhar com Unicode em Python. Ele inclui problemas de portabilidade, assim como questões que surgem quando lidamos com [http://www.w3.org/MarkUp/HTML HTML], [http://www.w3.org/XML/ XML] e sistemas de arquivo.

Aproveitando, Unicode é justamente simples, eu só queria ter aprendido a usá-lo corretamente da primeira vez.

Onde começar?

Em alto nível, computadores utilizam três tipos de representação de textos:

  1. ASCII
  2. Conjuntos de caracteres Multibyte
  3. Unicode

Eu acho que Unicode é mais fácil de entender se você entender como ele evoluiu a partir do código ASCII. A parte seguinte é umá breve sinópse desta evolução.

Do ASCII ao Multibyte

No início, existia ASCII. (OK, também existia o [http://www.dynamoo.com/technical/ascii-ebcdic.htm#asciibetter EBCDIC]), mas este nunca pegou fora dos mainframes, então eu o estou omitindo aqui). O conjunto de caracteres ASCII contém 256 caracteres, como você pode ver nesta [http://www.asciitable.com/ tabela ASCII]. Ainda que 256 caracteres sejam disponíveis, os primeiros 128 (códigos de 0 a 127) são normalmente os mais utilizados. Na verdade, os primeiros sistemas de email só permitiam a transmissão de caracteres 0-127 (isto é texto de "7-bits") e de fato isto continua valendo para muitos sistemas ainda hoje. Como você pode constatar na tabela, o código ASCII é suficiente apenas para documentos escritos em inglês.

Problemas começaram a surgir quando computadores começaram a ser usados em países onde não bastavam apenas os caracteres ASCII. O código ASCII não possui a capacidade de ser utilizado em textos escritos em grego, cirílico ou japonês, para citar poucos. Além disso, textos em japonês precisam de milhares de caracteres, logo não há como fazer isso usando apenas 8-bits. Para superar esta limitação, Conjuntos de Caracteres Multibyte foram inventados. A maioria (senão todos) dos Conjuntos de Caracteres Multibyte se aproveitam do fato de que apenas os 128 primeiros caracteres do código ASCII são comumente utilizados (códigos 0-127 em decimal, ou 0x00-0x7f em hexadecimal). Os códigos superiores (128..255 em decimal, ou 0x80-0xff em hexadecimal) são utilizados para definir conjuntos estendidos (não utilizados em inglês).

Vamos olhar um exemplo: Shift-JIS é uma das codificações para texto em japonês. Você pode ver a [http://www.rikai.com/library/kanjitables/kanji_codes.sjis.shtml tabela de caracteres aqui]. Observe que o primeiro byte de cada caractere começa com um valor hexadecimal entre 0x80 e 0xfc. Esta é uma propriedade interessante, porque ela faz com que texto em japonês e inglês possam ser misturados livremente! A string "Hello World!" é perfeitamente válida na codificação Shift-JIS para textos em inglês. Quando analisamos, caracter a caracter (parse), um texto que utiliza o Shift-JIS, se você encontrar um byte na faixa 0x80-0xff, você saberá que este é o primeiro caracter de uma seqüência de dois códigos. Caso contrário, é um caracter de apenas um byte como no ASCII comum.

Isto funciona muito bem enquanto você trabalhar apenas em japonês, mas o que acontece se você trocar para o [http://czyborra.com/charsets/iso8859.html#ISO-8859-7 conjunto de caracteres gregos]? Como você pode observar, na tabela do ISO-8859-7 os códigos de 0x80-0xff são definidos de uma forma completamente diferente do Shift-JIS. Logo, ainda que você possa misturar inglês com japonês, você não pode misturar grego e japonês uma vez que esses códigos se sobrepõem. Este é um problema comum ao se misturar conjuntos de caracteres multibyte.

De Multibyte a Unicode

Para resolver o problema de misturar linguagens diferentes, o código Unicode propõe combinar todos os conjuntos de caracteres do mundo em uma única tabela gigantesca. Dê uma olhada no [http://www.unicode.org/charts/ conjunto de caracteres Unicode].

De início, parecem existir tabelas diferentes para cada linguagem, assim você pode não perceber melhorias em relação ao ASCII. Na realidade, todos estão na mesma tabela, e estão agrupados aqui simplesmente para facilitar a referência(por humanos). A princial característica a observar é que uma vez que todos estes caracteres são parte da mesma tabela, não há sobreposição de código entre eles como ocorre no mundo ASCII/Multibyte. Isto permite a documentos Unicode misturar linguagens livremente sem conflitos de codificação.

Terminologia Unicode

Vamos olhar a [http://www.unicode.org/charts/PDF/U0370.pdf tabela de caracteres Gregos] a pegar alguns caracteres:

Exemplos de Símbolos Unicode

03A0

Π

Greek Capital Letter Pi (Grego Letra Maiúscula Pi)

03A3

Σ

Greek Capital Letter Sigma (Grego Letra Maiúscula Sigma)

03A9

Ω

Greek Capital Letter Omega (Grego Letra Maiúscula Omega)

É comum referenciar estes símbolos usando a notação U+NNNN, por exemplo U+03A0. Logo, nós poderíamos definir uma string que contenha estes caracteres usando a seguinte notação (eu adicionei colchetes para facilitar o entendimento):

uni = {U+03A0} + {U+03A3} + {U+03A9} 

Agora, mesmo que saibamos exatamente o que 'uni' representa (ΠΣΩ), observe que não como:

  • Imprimir uni na tela.
  • Salvar uni em um arquivo.
  • Dizer quantos bytes uni ocupa

Por que? Porque uni é uma string Unicode idealizada - nada mais que um conceito até agora. Veremos brevemente como imprimir, salvar e manipular, mas por enquanto, lembre-se desta última afirmação: Não há como dizer quantos bytes serão necessários para armazenar uni. De fato, você deve esquecer tudo sobre bytes e pensar em strings Unicode como conjuntos de símbolos.

Nome da Codificação

Representação Binária

ISO-8859-7

\xD9 (codificação grega nativa)

UTF-8

\xCE\xA9

UTF-16

\xFF\xFE\xA9\x03

UTF-32

\xFF\xFE\x00\x00\xA9\x03\x00\x00

Cada um destas representações é uma codificação válida de Ω, mas tentar trabalhar com bytes como acima não é melhor que lidar com o mundo ASCII/Multibyte. É por isso que eu digo que você deve pensar em Unicode como símbolos (Ω) e não como bytes.

Texto Unicode em Python

Para converter nossa string Unicode idealizada uni (ΠΣΩ) em de forma a poder ser utilizada, nós temos que observar algumas coisas:

  1. Representação de literais Unicode
  2. Convertendo Unicode para binário
  3. Convertendo binário para Unicode
  4. Usando operações com strings

Convertendo símbolos Unicode em literais Python

Criar uma string Unicode com símbolos é muito fácil. Vamos relembrar os símbolos gregos:

Exemplos de Símbolos Unicode

03A0

Π

Greek Capital Letter Pi (Grego Letra Maiúscula Pi)

03A3

Σ

Greek Capital Letter Sigma (Grego Letra Maiúscula Sigma)

03A9

Ω

Greek Capital Letter Omega (Grego Letra Maiúscula Omega)

Vamos dizer que nós queremos uma string Unicode com esses caracteres, mais alguns caracteres ASCII a moda antiga.

Pseudo-código:

uni = 'abc_' + {U+03A0} + {U+03A3} + {U+03A9} + '.txt'

Fazendo isto em Python:

uni = u"abc_\u03a0\u03a3\u03a9.txt"

Algumas coisas a observar:

  • Caracteres ASCII comuns podem ser escritos normalmente. Você pode simplesmente colocar "a", não precisa escrever o símbomo Unicode "\u0061". (Mas lembre-se, "a" é realmente {U+0061}; não existe essa coisa de símbolo Unicode "a".)

  • A seqüência de escape \u é usada para representar códigos Unicode.

    • Isto é algo como o tradicional estilo-C \xNN para inserir valores binários. Entretanto, uma olhada na tabela Unicode nos revela valores de até 6 dígitos. Estes não podem ser convenientemente representados por \xNN, assim o \u foi inventado.

    • Para valores Unicode até (e incluindo) 4 dígitos, use a versão de 4 dígitos: \uNNNN (Observe que você deve usar todos os 4 dígitos, usando zeros a esquerda se necessário).

    • Para valore Unicode maiores que 4 dígitos, use a versão de 8 dígitos: \UNNNNNNNN (Observe que você deve usar todos os 8 dígitos, usando zeros a esquerda se necessário)

Aqui está um outro exemplo:

Pseudo-código:

uni = {U+1A} + {U+B3C} + {U+1451} + {U+1D10C}

Python:

   1 uni = u'\u001a\u0bc3\u1451\U0001d10c'

Note como eu adicionei zeros a cada um destes valores para que eles tivessem de 4 a 8 dígitos. Você receberá um error do Python se não fizer isso. Observe também que você pode usar tanto letras maiúsculas quanto minúsculas no código. O exemplo abaixo resultaria exatamente na mesma coisa:

Python:

   1 uni = u'\u001A\u0BC3\u1451\U0001D10C'

Por que o "print" não funciona?

Você lembra que anteriormente eu disse que uni não possuía uma representação determinada no computador. Então, o que acontece se nós tentarmos imprimir uni ?

   1 uni = u"\u001A\u0BC3\u1451\U0001D10C"
   2 print uni

Você veria:

Traceback (most recent call last):
  File "t6.py", line 2, in ?
    print uni
UnicodeEncodeError: 'ascii' codec can't encode characters in position 1-4:
ordinal not in range(128)

O que aconteceu? Bem, você pediu ao Python para imprimir uni, mas uma vez que uni não tem uma representação determinada no computador, Python tem que primeiro converter uni em alguma forma imprimível. Já que você não disse ao Python como fazer a conversão, ele assumiu que você queria ASCII. Infelizmente, ASCII só manipula valores entre 0 e 127, e uni contém valores fora da faixa, por isso você tem um erro.

Um método rápido de imprimir uni é usar o método do Python chamado repr():

uni = u"\u001A\u0BC3\u1451\U0001D10C"
print repr(uni)

Que imprime:

u'\x1a\u0bc3\u1451\U0001d10c'

Que, claro, faz sentido, uma fez que é exatamente como nós definimos uni. Mas repr(uni) é simplesmente inútil no mundo real assim como uni. O que nós realmente precisamos é aprender sobre codecs.

Codecs

Codecs Em geral, os codecs da linguagem Python permitem transformações arbitrárias entre objetos. Entretanto, no contexto deste artigo, é suficiente entender codecs como funções que transformam objetos Unicode em strings Python em formato binário, e vice-versa.

Por que precisamos deles? Objetos Unicode não possuem uma representação determinada no computador. Antes que um objeto Unicode possa ser impresso, armazenado em disco, ou enviado pela rede, ele deve ser codificado em um representação especifica. Isto é feito utilizando-se um codec. Alguns codecs populares que você pode ter ouvido falado no seu dia-a-dia: ascii, iso-8859-7, UTF-8, UTF-16.

De Unicode para binário

Para converter um valor Unicode em uma representação binária, você chama o método .encode com o nome do codec. Por exemplo, para converter um valor Unicode par UTF-8:

binary = uni.encode("utf-8")

O que você acha tornarmos uni mais interessante, adicionando alguns caracteres comuns:

uni = u"Hello\u001A\u0BC3\u1451\U0001D10CUnicode" 

Agora, vamos observar como diferente codecs representam uni. Aqui um pequeno programa de teste:

test_codec01.py

   1 if __name__ == '__main__':
   2 
   3     # Define nossa string Unicode
   4     uni = u"Hello\u001A\u0BC3\u1451\U0001D10CUnicode"
   5 
   6     # UTF-8 e UTF-16 podem codificar completamente qualquer string Unicode
   7     print "UTF-8", repr(uni.encode('utf-8'))
   8     print "UTF-16", repr(uni.encode('utf-16'))
   9 
  10     # ASCII pode apenas codificar valore entre 0-127. Abaixo, dizemos ao Python
  11     # para trocar caracteres que não podem ser codificados por '?'
  12     print "ASCII",uni.encode('ascii','replace')
  13 
  14     # ISO-8859-1 é similar ao ASCII
  15     print "ISO-8859-1",uni.encode('iso-8859-1','replace')

Que produz a seguinte saída:

UTF-8 'Hello\x1a\xe0\xaf\x83\xe1\x91\x91\xf0\x9d\x84\x8cUnicode'
UTF-16 '\xff\xfeH\x00e\x00l\x00l\x00o\x00\x1a\x00\xc3\x0bQ\x144
        \xd8\x0c\xddU\x00n\x00i\x00c\x00o\x00d\x00e\x00'
ASCII Hello????Unicode
ISO-8859-1 Hello????Unicode

Note que eu continuei a usar repr() para imprimir as strings UTF-8 e UTF-16. Por que? Bem, de outra forma os valores teriam sido impressos na tela utilizando seu conteúdo binário, o que seria difícil de mostrar neste documento.

De binário para Unicode

Digamos que alguém tenha dado a você um objeto Unicode codificado com UTF-8. Como converter de volta para Unicode? Você pode ingenuamente tentar isso:

A forma ingênua (e errada)

|| uni = unicode( utf8_string )

Por que errada? Aqui temos um programa que faz exatamente isso:

   1 uni = u"Hello\u001A\u0BC3\u1451\U0001D10CUnicode"
   2 utf8_string = uni.encode('utf-8')
   3 
   4 # Ingenuamente converte de volta para Unicode
   5 uni = unicode(utf8_string)

Aqui o que acontece:

Traceback (most recent call last):
    File "t6.py", line 5, in ?
    uni = unicode(utf8_string)

    UnicodeDecodeError: 'ascii' codec can't decode byte 0xe0
    in position 6: ordinal not in range(128)

Você sabe, a função unicode() possui realmente dois parâmetros:

   1 def unicode(string, encoding):
   2      ....

No exemplo acima, nós omitimos a codificação (encoding) logo o Python, na melhor das intenções, assumiu mais uma vez que nós queríamos ASCII (nota 1), e nos deu a coisa errada.

Aqui está a forma correta de fazer isso:

   1 uni = u"Hello\u001A\u0BC3\u1451\U0001D10CUnicode"
   2 utf8_string = uni.encode('utf-8')
   3 
   4 # Tem que decodificar usando o mesmo codificador usado na codificação!
   5 uni = unicode(utf8_string,'utf-8')
   6 print "De volta para UTF-8: ",repr(uni)

Que resulta em:

De volta para UTF-8:  u'Hello\x1a\u0bc3\u1451\U0001d10cUnicode'

Operações com Strings

Os exemplos acima devem ter dado uma boa idéia do por quê você quer evitar tratar valores Unicode em formato binário o máximo possível! A versão UTF-8 tinha 23 bytes, a versão UTF-16 tinha 36 bytes, a versão ASCII tinha 16 bytes (mas ela descartou completamente 4 valores Unicode) e de forma similar com ISO-8859-1.

Isto é o por quê, desde o começo, sugeri que você esquecesse sobre bytes!

A boa nova é que uma vez que você tem um objeto Unicode, ele se comporta exatemente como um objeto string comum, logo não há sintaxe adicional a aprender (outra que os códigos de escape \u e \U). Aqui temos um pequeno exemplo que mostra objetos Unicode se comportando do jeito que você espera:

test_strings01.py

   1 if __name__ == '__main__':
   2 
   3     uni = u"Hello\u001A\u0BC3\u1451\U0001D10CUnicode"
   4 
   5     print "uni = ",repr(uni)
   6 
   7     print "len(uni) = ",len(uni)
   8 
   9     # print the "Hello" part
  10     print "uni[:5] = ",uni[:5]
  11 
  12     # print the Unicode characters one at a time
  13     print "uni[5] = ",repr(uni[5])
  14     print "uni[6] = ",repr(uni[6])
  15     print "uni[7] = ",repr(uni[7])
  16 
  17     # Depending on how Python was compiled, \U characters
  18     # may be stored as two Unicode characters -- see the
  19     # section "A wrinkle in \U" below for more details ...
  20     print "uni[8] = ",repr(uni[8])
  21     print "uni[9] = ",repr(uni[9])
  22 
  23     # print the "Unicode" text at the end
  24     print "uni[10:] = ",repr(uni[10:])

Executando este exemplo, obtemos o seguinte resultado:

uni =  u'Hello\x1a\u0bc3\u1451\U0001d10cUnicode'
len(uni) =  17
uni[:5] =  Hello
uni[5] =  u'\x1a'
uni[6] =  u'\u0bc3'
uni[7] =  u'\u1451'
uni[8] =  u'\ud834'
uni[9] =  u'\udd0c'
uni[10:] =  u'Unicode'

O pau do \U

Dependendo de como seu interpretador Python foi compilado, ele armazena objetos Unicode internamente em UTF-16 (2 bytes por caracter) ou UTF-32 (4 bytes por caracter). Infelizmente este nível de detalhe é exposto na interface normal de string.

Para caracteres de 4 dígitos(16 bits) como \u03a0, não há diferença.

   1 a = u'\u03a0'
   2 print len(a)

Mostrará tamanho 1, não importa como o seu Python foi compilado, e a[0] será sempre \u03a0. Entretanto, para caracteres de 8 dígitos (32 bits), como \U0001FF00, você verá uma diferença. Obviamente, valores de 32 bits não podem ser diretamente representados em código de 16 bits, logo um par de valores de 16 bits é usado. (Códigos 0xD800 - 0xDFFF), chamdos "surrogate pairs", são reservados para estas sequências de dois caracteres. Estes valores são invalidos se utilizados separadamente pela especificação Unicode.)

A programa exemplo que mostra o que acontece:

O que acontece com \U...

   1 a = u'\U0001ff00'
   2 print "Length:",len(a)
   3 
   4 print "Chars:"
   5 for c in a:
   6     print repr(c)

Se você executar este exemplo em um Python UTF-16, você verá:

Resultado, Python UTF-16

Length: 2
Chars:
u'\ud83f'
u'\udf00'

Em um Python UTF-32, você verá:

Resultado com Python UTF-32:

Length: 1
Chars:
u'\U0001ff00'

Este é um detalhe irritante de se preocupar. Eu escrevi um módulo que deixa você passar caracter a caracter dentro de uma string Unicode, independete de você utilizar Python UTF-16 ou Python UTF-32. Ele é chamado xmlmap e é parte dos [http://freshmeat.net/projects/gnosisxml/ utilitários Gnosis]. Aqui estão dois exemplos, um usando o xmlmap e outro não.

Sem xmlmap

   1 a = u'A\U0001ff00C\U0001fafbD'
   2 print "Length:",len(a)
   3 
   4 print "Chars:"
   5 for c in a:
   6     print repr(c)

Resultados sem o xmlmap, em um Python UTF-16

Length: 7
Chars:
u'A'
u'\ud83f'
u'\udf00'
u'C'
u'\ud83e'
u'\udefb'
u'D'

Agora, usando a função usplit() para conseguir os caracteres um por um, combinando valores quando necessário:

Com xmlmap

   1 from gnosis.xml.xmlmap import usplit
   2 
   3 a = u'A\U0001ff00C\U0001fafbD'
   4 print "Length:",len(a)
   5 
   6 print "Chars:"
   7 for c in usplit(a):
   8     print repr(c)

Resultados com xmlmap, em um Python UTF-16

Length: 7
Chars:
u'A'
u'\U0001ff00'
u'C'
u'\U0001fafb'
u'D'

Agora você terá resultados idênticos, independente de como seu interpretador Python foi compilado. (Note que o tamanho continua o mesmo, mas usplit() combinou os "surrogate pairs" de forma que você não os vê aqui.)

Bugs do Python 2.0 & 2.1

Você pode pensar quem liga quando isso vem do Python 2.0 e 2.1, mas ao escrevermos código supostamente portátil, isso importa!

O Python 2.0.x e 2.1.x tem um erro fatal quando tenta manipular códigos de um caracter na faixa \uD800-\uDFFF.

O exemplo abaixo apresenta o problema:

   1  u = unichr(0xd800)
   2  print "Orig: ",repr(u)
   3 
   4  # Cria utf-8 a partir de '\ud800'
   5  ue = u.encode('utf-8')
   6  print "UTF-8: ",repr(ue)
   7 
   8  # Decodifica de volta para Unicode
   9  uu = unicode(ue,'utf-8')
  10  print "Back: ",repr(uu)

Rodando isso no Python 2.2 e versões superiores produz o resultado esperado:

 Orig:  u'\ud800'
 UTF-8:  '\xed\xa0\x80'
 Back:  u'\ud800'

Python 2.0.x retorna:

 Orig:  u'\uD800'
 UTF-8:  '\240\200'
 Traceback (most recent call last):
   File "test_utf8_bug.py", line 9, in ?
     uu = unicode(ue,'utf-8')
 UnicodeError: UTF-8 decoding error: unexpected code byte

Python 2.1.x retorna:

 Orig:  u'\ud800'
 UTF-8:  '\xa0\x80'
 Traceback (most recent call last):
   File "test_utf8_bug.py", line 9, in ?
     uu = unicode(ue,'utf-8')
 UnicodeError: UTF-8 decoding error: unexpected code byte

Como voocê pode ver, ambos falharam ao codificar \ud800 quando usado como um caracter único. Ainda que seja verdade que caracteres entre 0xD800 .. 0xDFF não sejam válidos quando usados sozinhos, o fato é que Python lhe deixa usá-los.

Mas se são inválidos, por que o Python se importa?

Eu arranjei um bom exemplo, completamente por acidente enquanto trabalhava no código deste tutorial. Crie dois arquivos em Python:

aaa.py

   1 x = u'\ud800'

bbb.py

   1 import sys
   2 sys.path.insert(0,'.')
   3 import aaa

Agora, use o Python 2.0.x/2.1.x para executar bbb.py duas vezes (precisa executar duas vezes para que ele carregue aaa.pyc na segunda vez). Na segunda execução, você terá:

Traceback (most recent call last):
    File "bbb.py", line 3, in ?
      import aaa
  UnicodeError: UTF-8 decoding error: unexpected code byte

É isso mesmo: o Python 2.0.x/2.1.x não é capaz de recarregar seu próprio bytecode de um arquivo .pyc se o fonte contém uma string como \u0d800. Uma forma de resolver este problema seria usar unichr(0xd800) no lugar de \ud800 (e é isto que o gnosis.xml.pickle faz).

Python como um "recodificador universal"

Até este ponto, eu converti Unicode de e para UTF para fins de demonstração. Entretanto, Python lhe permite fazer muito mais que isso. Python permite que você converta praticamente qualquer string multibyte em Unicode (e vice-versa). Implementando todas estas conversões dá um monte de trabalho. Felizmente, já foi feito, tudo que temos que fazer é usar.

Vamos revisitar nossa tabela grega, mas desta vez eu vou apresentar os caracteres tanto em Unicode quanto em ISO-8859-7 ("grego nativo").

Caracter

Nome

Unicode

ISO-8859-7

Π

Greek Capital Letter Pi (Grego Letra Maiúscula Pi)

03A0

0xD0

Σ

Greek Capital Letter Sigma (Grego Letra Maiúscula Sigma)

03A3

0xD3

Ω

Greek Capital Letter Omega (Grego Letra Maiúscula Omega)

03A9

0xD9

Com Python, usando unicode() e .encode() é trivial converter entre eles.

   1 # {Pi}{Sigma}{Omega} como uma string codificada com ISO-8859-7
   2 b = '\xd0\xd3\xd9'
   3 
   4 # Converte para Unicode ('formato universal')
   5 u = unicode(b, 'iso-8859-7')
   6 print repr(u)
   7 
   8 # ... e de volta para ISO-8859-7
   9 c = u.encode('iso-8859-7')
  10 print repr(c)

Mostra:

u'\u03a0\u03a3\u03a9'
\xd0\xd3\xd9

Você também pode usar Python como um "recodificador universal". Digamos que você tenha recebido um arquivo em japonês, usando a codificação [http://www.rikai.com/library/kanjitables/kanji_codes.sjis.shtml ShiftJIS] e queira convertê-lo para a codificação [http://www.rikai.com/library/kanjitables/kanji_codes.euc.shtml EUC-JP]:

   1 txt = ... texto codificado com ShiftJIS ...
   2 
   3 # converte para Unicode ("formato universal")
   4 u = unicode(txt, 'shiftjis')
   5 
   6 # converte para EUC-JP
   7 out = u.encode('eucjp')

Claro que isto só funciona quando convertemos entre conjuntos de caracteres compatíveis. Tentar converter entre conjuntos de caracteres japoneses e gregos desta forma não funcionaria.

Agora começa a diversão... Unicode e o Mundo Real

Agora você já sabe tudo que precisa para trabalhar com objetos Unicode em Python. E isso é muito legal, não? Entretanto, o resto do mundo não é tão bacana quanto Python, você precisa saber como a porção do mundo não-Python suporta Unicode. Não é tão difícil, mas há muitos casos especiais a considerar.

De agora em diante, nós estaremos lidando com problemas que surgem quando trabalhamos com Unicode e:

  1. Nomes de arquivos (problemas específicos de sistemas operacionais)
  2. XML
  3. HTML
  4. Compartilhamento de rede (Samba)

Nomes de arquivo com caracteres Unicode

Parece simples, certo? Se você quiser nomear um arquivo com minhas letras gregas, eu diria:

open(unicode_name, 'w')

Teoricamente, sil, isto é supostamente tudo que deveríamos fazer. Mas, há muitas formas disto não funcionar, e elas dependem da plataforma que seu programa está rodando.

Microsoft Windows

Existem pelo menos duas formas de rodar Python em Windows. A primeira delas é usar os arquivos compilados para Win32 da [http://www.python.org/ www.python.org]. Eu vou me referir a este método como "Python nativo-Windows".

O outro método é usar a versão do Python que vem com o [http://www.cygwin.org/ Cygwin]. Esta versão do Python se parece mais (para o código do usuário) com POSIX do que um ambiente nativo Windows.

Para muitas coisas, as duas versões são compatíveis. Uma vez que se você escreve código Python multiplataforma, não deveria se preocupar com que interpretador você estará rodando. Porém, uma importante exceção é o suporte Unicode. Isto é o por quê de eu ser específico aqui sobre qual versão eu estou rodando.

Usando Python nativo-Windows

Vamos continuar a usar nossa tabela se símbolos gregos:

Exemplos de Símbolos Unicode

03A0

Π

Greek Capital Letter Pi (Grego Letra Maiúscula Pi)

03A3

Σ

Greek Capital Letter Sigma (Grego Letra Maiúscula Sigma)

03A9

Ω

Greek Capital Letter Omega (Grego Letra Maiúscula Omega)

Nosso nome de arquivo Unicode exemplo será:

   1 # this is: abc_{PI}{Sigma}{Omega}.txt
   2 uname = u"abc_\u03A0\u03A3\u03A9.txt"

Vamos criar um arquivo com este nome, contendo uma única linha de texto:

   1 open(uname,'w').write('Hello world!\n')

Abrir uma janela do Explorer mostra os resultados (clique na imagem para uma versão maior):

[http://boodebr.org/python/pyunicode/win32_01.png http://boodebr.org/python/pyunicode/win32_01.jpg]

Lá está o nome do arquivo com toda sua glória Unicode.

Agora, vamos ver como os.listdir() funciona com este nome. A primeira coisa a saber é que os.listdir() tem dois modos de operação:

  • Não-unicode, obtido ao se passar uma string não Unicode para os.listdir(), exemplo: os.listdir('.')

  • Unicode, obtido ao se passar uma string Unicode para os.listdir(), exemplo: os.listdir(u'.')

Primeiro, vamos tentar como Unicode:

   1 os.chdir('ttt')
   2 # Há apenas um arquivo no diretório 'ttt'
   3 name = os.listdir(u'.')[0]
   4 print "Nome: ",repr(name)
   5 print "Linha: ",open(name,'r').read()

Executando-se este programa temos o seguinte resultado:

Nome:  u'abc_\u03a0\u03a3\u03a9.txt'
Linha:  Hello world!

Comparando acima, parece correto. Observe que print repr(name) foi requerido, uma vez que teria ocorrido um erro caso eu tentasse imprimir o nome diretamente na tela. Por que? Ora, mais uma vez o Python teria assumido que você queria uma codificação ASCII, e teria então falhado com um erro.

Agora vamos tentar o exemplo acima novamente, mas usando a versão não-Unicode de os.listdir():

   1 os.chdir('ttt')
   2 # Há apenas um arquivo no diretório 'ttt'
   3 name = os.listdir('.')[0]
   4 print "Nome: ",repr(name)
   5 print "Linha: ",open(name,'r').read()

Resulta na seguinte saída:

Nome:  'abc_?SO.txt'
Linha: 
Traceback (most recent call last):
  File "c:\frank\src\unicode\t2.py", line 8, in ?
    print "Line: ",open(name,'r').read()
IOError: [Errno 2] No such file or directory: 'abc_?SO.txt'

Credo! O que aconteceu? Bem vindo ao maravilhoso mundo "API-dupla" win32.

Um pouco sobre isso:

  • O Windows NT/2000/XP sempre escreve nomes de arquivo usando Unicode no sistema de arquivos (nota 2). Então, em teoria, nomes de arquivo Unicode deveriam funcionar perfeitamente em Python. Infelizmente, a win32 fornece duas APIs para fazer a interface com o sistema de arquivos. E usando o legítimo estilo Microsoft, elas são incompatíveis. As duas APIs são:
    1. Uma APIs para aplicações que suportam Unicode, que retornam nomes Unicode de verdade.
    2. Uma API para aplicações que não suportam Unicode que retornam uma representação dos nomes de arquivo Unicode usando uma codificação dependente do [http://en.wikipedia.org/wiki/Locale local]. O Python (para melhor ou pior) segue estas convenções na plataforma win32, então você acaba ficando com duas formas incompatíveis de chamar os.listdir() e open():

    3. Quando você chama os.listdir(),open(), etc. com uma string Unicode, o Python chama a versão Unicode da API e você recebe os nomes de arquivo em Unicode de verdade. (Isto corresponde a primeira API que falamos acima).

    4. Quando você chama os.listdir(),open(), etc. com uma string não-Unicode, o Python chama a versão não-Unicode da API, e é aqui que mora o problema. A API não-Unicode suporta Unicode com um codec particular chamado MBCS. MBCS é um codec que perde informação no processo: todo nome MBCS pode ser representado como Unicode, mas o contrário não é válido. A codificação MBCS também muda dependendo das configurações de local da máquina. Em outras palavras, se eu escrever um CD com um arquivo cujo nome possua caracteres multibytes em MBCS na minha máquina configurada para Inglês, e depois enviar o CD para o Japão, o nome do arquivo pode aparecer com caracteres completamente diferentes.

Agora que falamos do que há por trás disso, nós podemos entender o que aconteceu acima. Ao usarmos os.listdir('.', você estará recebendo a versão MBCS do nome Unicode que está armazenado no sistema de arquivos. E, na minha máquina configurada para Inglês, não existe um mapeamento preciso para os caracteres gregos, e você acaba com "?", "S" e "O". Isto nos leva ao segundo resultado estranho que é não haver jeito de abrir nosso arquivo com letras gregas usando a API MBCS em uma máquina configurada com os parâmetros de localização em inglês(!!).

Fundo do poço Eu recomendo sempre usar strings Unicode em os.listdir(), open(), etc. Lembre-se que o Windows NT/2000/XP sempre armazena os nomes de arquivo como Unicode, e esse é o comportamento padrão. E, como mostrado acima, pode algumas vezes ser a única forma de abrir um arquivo com nome em Unicode.

Perigo ! Cygwin

O Cygwin tem um grande problema aqui. Ele (pelo menos por enquanto) não tem suporte a Unicode. Isto é, ele nunca irá chamar as versões Unicode da API win32. Assim, é impossível abrir certos arquivos (como o nosso arquivo com caracteres gregos no nome) no Cygwin. Não importa se você usa os.listdir(u'.') ou os.listdir('.'); você sempre receberá a versão codificada em MBCS.

Note também que isto não é um problema específico do Python; é um problema sistemático com Cygwin. Todos os utilitários Cygwin como zsh, ls, zip, unzip, mkisofs, não serão capazes de reconhecer um nome de arquivo com letras gregas, e irão reportar diversos errors.

Unix/POSIX/Linux

Mac OS/X

Unicode e HTML

Unicode e XML

Unicode e diretórios compartilhados (Samba)

Sumário