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

Diferenças para "TudoSobrePythoneUnicode"

Diferenças entre as versões de 8 e 9
Revisão 8e 2008-02-22 14:56:06
Tamanho: 32977
Editor: NiloMenezes
Comentário:
Revisão 9e 2008-02-22 16:35:54
Tamanho: 39007
Editor: NiloMenezes
Comentário:
Deleções são marcadas assim. Adições são marcadas assim.
Linha 657: Linha 657:

Diferente do Windows NT/2000/XP, que sempre gravam os nomes de arquivo em Unicode, os sistemas POSIX (incluindo o Linux) sempre armazenam os nomes de arquivo como strings binárias. Isto é de alguma forma mais flexível, uma vez que o sistema operacional não precisa saber (ou se preocupar) com qual codificação é utilizada nos nomes de arquivo. A desvantagem é que o usuário é responsável por configurar seu ambiente ("locale") para a codificação apropriada.

===== Configurando o local =====

Os detalhes de como configurar seu POSIX para suportar nomes de arquivo Unicode estão além do escopo deste documento, mas geralmente é feito pela configuração de algumas variáveis de ambiente. No meu caso, eu quis usar a codificação UTF-8 em um local inglês EUA, logo minha configuração envolveu adicionar algumas linhas aos meus arquivos de inicialização (eu tentei isso no Gentoo e Ubuntu), mas deve ser parecido em todos os sistemas Linux):

Adições ao '''.bashrc''':
{{{
LANG="en_US.utf8"
LANGUAGE="en_US.utf8"
LC_ALL="en_US.utf8"

export LANG
export LANGUAGE
export LC_ALL
}}}

Por precaução, eu adicionei essas mesmas linhas ao meu arquivo '''.zshrc'''.

Adicionalmente, eu adicionei as primeiras três linhas ao '''/etc/env.d/02locale'''.

|| Aviso ||
|| '''Por favor''' não faça modificações como as acima em seu sistema se você não tem certeza do que está fazendo. Você pode tornar seus arquivos ilegíveis alternando locais. O exemplo acima é apenas um caso simples de se alterar o local de ASCII para UTF-8 ||

==== Python no Posix ====

Uma grande vantagem no Posix, no que diz respeito ao Python, é que você pode usar tanto:
{{{
os.listdir('.')
}}}
ou
{{{
os.listdir(u'.')
}}}
Ambos retornarão strings que você pode passar para {{{open()}}} para abrir arquivos. Isto é muito melhor do que no Windows, que retornaria uma versão capada dos nomes se você usar {{{os.listdir('.')}}}, que como vimos acima pode algumas vezes falhar em retornar um nome de arquivo válido para abertura. Você sempre tem um nome válido no POSIX/Linux.

Aqui está uma pequena função para demostrar isso:

'''test_posix01.test()'''
{{{#!python numbering=off
def test():
    # Demonstra que listdir(u'.') e listdir('.')
    # funcional no POSIX(diferente do win32)

    import os

    uname = u'abc_\u03a0\u03a3\u03a9.txt'

    # Cria um diretório temporário para que tenhamos apenas um arquivo nele
    os.mkdir('ttt')
    os.chdir('ttt')

    open(uname,'w').write("Hello unicode!\n")

    # usa listdir() para obter o nome como Unicode
    name = os.listdir(u'.')[0]
    print "Como unicode: ",repr(name)
    print " Linha lida: ",open(name,'r').read()

    # agora pega o nome como string de bytes
    name = os.listdir('.')[0]
    print "Como string de bytes: ",repr(name)
    print " Linha lida: ",open(name,'r').read()

}}}
Se você executá-lo, você obterá:
{{{
Como unicode: u'abc_\u03a0\u03a3\u03a9.txt'
  Linha lida: Hello unicode!

Como string de bytes: 'abc_\xce\xa0\xce\xa3\xce\xa9.txt'
          Linha lida: Hello unicode!
}}}

Como você pode ver, nós fomos capazes de ler o arquivo com sucesso, não importa se usamos o nome do arquivo na versão Unicode ou a sring de bytes.

==== Demonstrações de Aplicação ====
Diferente do mundo Microsoft Windows onde você basicamente tem uma "janela DOS" e o Windows Explorer, no Linux você tem muitas alternativas para terminais e gerenciadores de arquivos que você quer usar. Isto é tanto uma benção quanto uma maldição: uma benção porque você pode pegar a aplicação que melhor se adapta a suas preferências, mas uma maldição em relação ao fato de que nem todas as aplicações tem o mesmo nível de suporte a Unicode.

Abaixo temos uma pesquisa de várias aplicações populares para ver o que elas suportam.

===== Aplicações que suportam nomes de arquivo Unicode =====
Meu favorito é '''mlterm''', um terminal multilíngue (clique para uma versão maior):

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

O terminal GNOME ('''gnome-terminal'''):

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

O terminal KDE ('''konsole'''):

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

Uma versão modificada do rxvt('''rxvt-unicode''') suporta Unicode, porém tem alguns problemas com sublinhas ("_") na fonte que eu escolhi...

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

Aqui temos nosso nome de arquivo com letras gregas em uma janela do gerenciador de arquivido do KDE (konqueror):

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

Aqui o gerenciador de arquivos do GNOME (Nautilus):

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

O gerenciador de arquivos do XFCE 4:

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

A janela de abertura arquivos padrão do KDE suporta nomes de arquivo Unicode:

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

Assim como a janela de abertura de arquivos do GNOME:

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

===== Aplicações que não suportam nomes de arquivo Unicode =====
 
O rxvt padrão não suporta Unicode corretamente:
 
 [http://boodebr.org/python/pyunicode/rxvt_01.png http://boodebr.org/python/pyunicode/rxvt_01.jpg]
 
O gerenciador de arquivos XFM não suporta nomes de arquivo Unicode:
 
 [http://boodebr.org/python/pyunicode/xfm_01.png http://boodebr.org/python/pyunicode/xfm_01.jpg]


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

Diferente do Windows NT/2000/XP, que sempre gravam os nomes de arquivo em Unicode, os sistemas POSIX (incluindo o Linux) sempre armazenam os nomes de arquivo como strings binárias. Isto é de alguma forma mais flexível, uma vez que o sistema operacional não precisa saber (ou se preocupar) com qual codificação é utilizada nos nomes de arquivo. A desvantagem é que o usuário é responsável por configurar seu ambiente ("locale") para a codificação apropriada.

Configurando o local

Os detalhes de como configurar seu POSIX para suportar nomes de arquivo Unicode estão além do escopo deste documento, mas geralmente é feito pela configuração de algumas variáveis de ambiente. No meu caso, eu quis usar a codificação UTF-8 em um local inglês EUA, logo minha configuração envolveu adicionar algumas linhas aos meus arquivos de inicialização (eu tentei isso no Gentoo e Ubuntu), mas deve ser parecido em todos os sistemas Linux):

Adições ao .bashrc:

LANG="en_US.utf8"
LANGUAGE="en_US.utf8"
LC_ALL="en_US.utf8"

export LANG
export LANGUAGE
export LC_ALL

Por precaução, eu adicionei essas mesmas linhas ao meu arquivo .zshrc.

Adicionalmente, eu adicionei as primeiras três linhas ao /etc/env.d/02locale.

Aviso

Por favor não faça modificações como as acima em seu sistema se você não tem certeza do que está fazendo. Você pode tornar seus arquivos ilegíveis alternando locais. O exemplo acima é apenas um caso simples de se alterar o local de ASCII para UTF-8

Python no Posix

Uma grande vantagem no Posix, no que diz respeito ao Python, é que você pode usar tanto:

os.listdir('.')

ou

os.listdir(u'.')

Ambos retornarão strings que você pode passar para open() para abrir arquivos. Isto é muito melhor do que no Windows, que retornaria uma versão capada dos nomes se você usar os.listdir('.'), que como vimos acima pode algumas vezes falhar em retornar um nome de arquivo válido para abertura. Você sempre tem um nome válido no POSIX/Linux.

Aqui está uma pequena função para demostrar isso:

test_posix01.test()

   1 def test():
   2     # Demonstra que listdir(u'.') e listdir('.')
   3     # funcional no POSIX(diferente do win32)
   4 
   5     import os
   6 
   7     uname = u'abc_\u03a0\u03a3\u03a9.txt'
   8 
   9     # Cria um diretório temporário para que tenhamos apenas um arquivo nele
  10     os.mkdir('ttt')
  11     os.chdir('ttt')
  12 
  13     open(uname,'w').write("Hello unicode!\n")
  14 
  15     # usa listdir()  para obter o nome como Unicode
  16     name = os.listdir(u'.')[0]
  17     print "Como unicode: ",repr(name)
  18     print "  Linha lida: ",open(name,'r').read()
  19 
  20     # agora pega o nome como string de bytes
  21     name = os.listdir('.')[0]
  22     print "Como string de bytes: ",repr(name)
  23     print "          Linha lida: ",open(name,'r').read()

Se você executá-lo, você obterá:

Como unicode:  u'abc_\u03a0\u03a3\u03a9.txt'
  Linha lida:  Hello unicode!

Como string de bytes:  'abc_\xce\xa0\xce\xa3\xce\xa9.txt'
          Linha lida:  Hello unicode!

Como você pode ver, nós fomos capazes de ler o arquivo com sucesso, não importa se usamos o nome do arquivo na versão Unicode ou a sring de bytes.

Demonstrações de Aplicação

Diferente do mundo Microsoft Windows onde você basicamente tem uma "janela DOS" e o Windows Explorer, no Linux você tem muitas alternativas para terminais e gerenciadores de arquivos que você quer usar. Isto é tanto uma benção quanto uma maldição: uma benção porque você pode pegar a aplicação que melhor se adapta a suas preferências, mas uma maldição em relação ao fato de que nem todas as aplicações tem o mesmo nível de suporte a Unicode.

Abaixo temos uma pesquisa de várias aplicações populares para ver o que elas suportam.

Aplicações que suportam nomes de arquivo Unicode

Meu favorito é mlterm, um terminal multilíngue (clique para uma versão maior):

O terminal GNOME (gnome-terminal):

O terminal KDE (konsole):

Uma versão modificada do rxvt(rxvt-unicode) suporta Unicode, porém tem alguns problemas com sublinhas ("_") na fonte que eu escolhi...

Aqui temos nosso nome de arquivo com letras gregas em uma janela do gerenciador de arquivido do KDE (konqueror):

Aqui o gerenciador de arquivos do GNOME (Nautilus):

O gerenciador de arquivos do XFCE 4:

A janela de abertura arquivos padrão do KDE suporta nomes de arquivo Unicode:

Assim como a janela de abertura de arquivos do GNOME:

Aplicações que não suportam nomes de arquivo Unicode

O rxvt padrão não suporta Unicode corretamente:

O gerenciador de arquivos XFM não suporta nomes de arquivo Unicode:

Mac OS/X

Unicode e HTML

Unicode e XML

Unicode e diretórios compartilhados (Samba)

Sumário