Skip to content

UnicodeDecodeError: DON’T PANIC

30/06/2010

Uma das coisas mais chatas de programar em Python ou qualquer outra linguagem é ter de consertar bugs ligados a codificação de caracteres. Isso acontece na maioria das vezes porque é uma das partes que esperamos que funcione sem nossa intervenção. Pequenos gafanhotos, grandes ilusões. Este post tem algumas dicas para ajudar você a criar programas com menos erros desse tipo.

Os bugs em geral não são causados pela forma como a linguagem usada lida com charsets ou com a ausência deles Unicode. Isso é tão chato que na nova versão do Python, a 3.0, Guido Van Rossum, o criador da linguagem e Benevolente Ditador Vitalício do projeto, decidiu que tudo será Unicode. No PyS60, o interpretador Python usado no Symbian dos celulares Nokia, o Unicode é usado extensivamente, mesmo sendo baseado em versões anteriores da linguagem. Porquê? Afinal o que é esse Unicode que tanto falam?

Imagine uma tabela. Uma tabela gigante que associa caracteres a números, contendo praticamente todos os caracteres inventados pela espécie humana até hoje. Isso que você acabou de imaginar é a tabela de caracteres Unicode. O objetivo do Unicode é fornecer um meio consistente de manipular texto de vários sistemas de escrita diferentes. Graças a ele, hoje é possível, por exemplo ler a Wikipédia em árabe explicando o que é o alfabeto cirílico, e vice-versa, usando a mesma codificação.

Por outro lado, representar todos os caracteres como Unicode ocupa muito espaço, pois a tabela Unicode é muito grande, e consequentemente a quantidade de bits usadas para representar cada caractere seria muito maior do que outras codificações comummente usadas como ASCII e ISO-8858-1. Por isso existem os UTFs (Unicode Transformation Format), sendo UTF-8 o mais popular deles. Esses formatos de transformação definem formas mais compactas de traduzir os caracteres mais usados do padrão Unicode para binário, e formas menos compactas para os sistemas de escrita de alguns países longínquos que ficaram muito irritados com isso.😀

Em termos de programação, inclusive em Python, a maioria do tempo estamos lidando com caracteres codificados no padrão definido pela localidade do sistema operacional, mas existem várias bibliotecas que utilizam strings Unicode por padrão, como sqlite3 e Tkinter. E ao misturar strings binárias e Unicode é que começam a surgir os problemas. Na verdade, o problema é um só:

Unicode não tem representação binária

Antes de gravar uma string em um arquivo, transmitir pela rede e em muitos outros casos é preciso usar uma representação binária, ou seja, antes de fazer qualquer uma dessas coisas é preciso converter as strings Unicode em alguma representação binária conhecida, como UTF-8, ISO-8859-1 (Latin-1), ou até mesmo ASCII. Veja o que acontece ao tentar salvar uma string Unicode em um arquivo:

>>> f = file('unicode.txt','w')
>>> f.write(u'ß')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xdf' in position 0: ordinal not in range(128)

Por outro lado, isto funciona:

>>> f.write('ß')

Estranho, não? O que acontece é que no primeiro caso o ß (Eszett) estava em uma string Unicode, e no segundo caso ele estava codificado em binário, no meu caso, usando UTF-8. A pergunta que não quer calar é “O que diabos isso tem a ver com ASCII?” O Python, que estava com preguiça de te explicar o que é Unicode😛, converteu a sua string para ASCII (porque sys.getdefaultencoding() retorna ‘ascii’ no Python 2.x) para poder gravá-la no arquivo.

Isso não é problema nenhum, mas só se você for um estadunidense do norte, caso contrário você vai ter esses erros intermitentes (e esses são os erros mais chatos de consertar), porque suas strings nem sempre estarão dentro dos caracteres codificáveis em ASCII.

Outro caso em que esses erros ocorrem com frequência é na coerção de tipos entre strings binárias e Unicode. Ao misturar esses dois tipos em operações como concatenação, interpolação e etc o resultado é sempre uma string Unicode e, mais uma vez, o Python vai precisar decodificá-la usando a codificação padrão e o resto você já sabe. Finalmente, veja

As soluções

Usar Python 3

Na versão 3, como já mencionei no primeiro parágrafo, tudo é Unicode. O tipo str se tornou a mesma coisa que o unicode do Python 2, tanto que ao declarar um literal Unicode não é mais necessário a letra u antes de abrir aspas. Já o antigo str agora se chama bytes, para frisar que não deve ser usado para texto, e sim para dados intrinsecamente binários.

Além disso, no Python 3 sys.getdefaultencoding() retorna ‘utf-8’, o que significa que você provavelmente nunca mais vai ver um UnicodeDecodeError na vida, a não ser que more em alguns desses países longínquos que continuarão a ficar muito irritados com isso.😀 Eles geralmente usam coisas como Shift-JIS, UTF-16, etc para codificar suas strings em binário.

Por outro lado, a migração para Python 3 é lenta e dolorosa. A maioria das bibliotecas que você usa diariamente em Python ainda não tem um correspondente disponível para essa nova versão. Se não fosse por isso, todo mundo hoje não teria motivo para programar em Python 2. Por isso 11 entre 10 programadores Python preferem a próxima solução.

Usar mais Unicode

Simples, porém eficaz. Unicode está se tornando um padrão e nadar contra a correnteza dos padrões não é seguro, só vale a pena quando o padrão é ruim, e não é o caso. Transforme tudo o que for possível em Unicode (uma dica é converter tudo que tem acentos), o quanto antes possível no seu código, manipule sempre Unicode e só codifique em binário quando for realmente necessário.

Até mesmo na hora de ver o tamanho de uma string podem ocorrer erros se a string não for Unicode, uma vez que existem muitas codificações que usam mais de um byte (ou octeto, que seja) e em strings binárias o Python enxerga bytes e não caracteres. Veja você:

>>> len('ß')
2
>>> len(u'ß')
1

Para facilitar o manejo de arquivos e Unicode o pacote codecs da biblioteca padrão do Python 2 possui uma função chamada open, que funciona de forma parecida com a open embutida no interpretador, mas recebe o nome de uma codificação como argumento e ao ler um arquivo aberto por essa função ele automaticamente retornará strings Unicode e ao gravar ele receberá strings Unicode que serão transformadas para a codificação escolhida. A função open embutida no Python 3 funciona de forma idêntica a codecs.open do Python 2.

E lembre-se, o UTF-8 não é só o mais popular dos UTFs como também é a codificação mais popular da Internet. Se for pra adivinhar qual a codificação de alguma coisa, pode apostar que é UTF-8. Caso queira apostar com alguma segurança você pode usar a biblioteca chardet que tenta descobrir a codificação das strings.

Agora você já sabe: da próxima vez que ver um UnicodeDecodeError lembre-se desse artigo e, principalmente, NÃO ENTRE EM PÂNICO.

Fontes: Farmdev, Python, Python Brasil, Wikipedia

8 Comentários leave one →
  1. jimmyskull permalink
    30/06/2010 20:11

    O mais difícil é quando a aplicação utiliza a internet e recebe dados de várias fontes. É difícil adivinhar qual é a codificação dos dados enviados..

    • 03/07/2010 22:44

      Não dá pra evitar problemas na hora de decodificar e codificar os dados, mas você pode eliminar essas preocupações da lógica da sua aplicação se manipular Unicode, deixando esses problemas de codificação para resolver apenas nas pontas da aplicação, no seu caso, na hora de ler da internet e enviar os dados para a pilha IRC.

  2. 01/07/2010 10:48

    ESTOU EM PÂNICO E AGORA?! :p Problemas de codificação, para mim, sempre existirá. Padronizar é difícil, principalmente quando a rotatividade dos padrões vigentes é grande.

    • 03/07/2010 22:47

      Quanto a padronização, a Web quase toda usa UTF-8, agora, se os programas usam Unicode ou não não influencia muito na padronização, já que o texto em Unicode poder ser codificado de várias formas em binário. O que importa é que usando Unicode os sistemas ficam mais interoperáveis, flexíveis e confiáveis.

  3. Luiz permalink
    09/07/2010 10:24

    Muito bom o artigo.
    Eu estava procurando algo relativo a esse assunto porque em Java eu consigo tratar a minha String sem problemas, já em Python… O problema acontece quando eu tenho uma String acentuada e quero retirar os acentos. Se eu coloco “eleição” ela retorna “eleicao” em Java, mas Python não consegue tratar essa ‘ç’.
    O método é esse:
    def remover_acentos(txt):
    return normalize(‘NFKD’,txt.decode(‘utf-8’)).encode(‘utf-8’)
    E eu não entendo como o caracter ‘ç’, presente em todas as tabelas pode dar problemas..

    • 27/07/2010 9:39

      Obrigado! Ainda não conhecia essa função normalize nem o módulo unicodedata.

      PS: você já deve saber, mas o ‘ç’ não faz parte do conjunto ASCII adotado pelo Python (só do estendido).😛

Trackbacks

  1. Tweets that mention UnicodeDecodeError: DON’T PANIC « iCaju -- Topsy.com
  2. UnicodeDecodeError: DON’T PANIC

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: