|
|
- Desculpe por isso me tomar tanto tempo, mas eu tenho peças realmente estranhas. |
UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xd1 in position 1: ordinal not in range(128) (Por que isso é tão difícil??)
Uma das coisas mais difíceis de acertar em um programa Python é a manipulação Unicode. Se você está lendo isto, provavelmente está no meio de descobri-lo da maneira mais difícil.
As principais razões da manipulação Unicode ser difícil no Python é porque a terminologia existente é confusa, e porque muitos casos que poderiam ser problemáticos são manipulados de maneira transparente. Isso impede que muitas pessoas tenham que aprender o que realmente está acontecendo, até que subitamente eles se deparam com um muro de tijolos quando querem manipular dados que contenham caracteres fora do conjunto de caracteres ASCII.
Se você acabou de correr para a parede de tijolos do Unicode no Python 2, aqui estão três passos que você pode pegar para começar a pensar sobre strings e Unicode da forma certa:
1. str é para bytes, NÃO strings
O primeiro passo para resolver o seu problema com Unicode é parar de pensar do tipo <'str'> como armazenamento de strings (isso é, sequência de caracteres legíveis a humanos, também conhecido como texto). Ao invés disso, comece a pensar do tipo <'str'> como um contêiner para bytes. Objetos do tipo <'str'> ficarão de fato perfeitamente felizes em armazenar sequências arbitrarias de bytes.
Para começar, dê uma olhada nas strings literais em seu código. Toda vez que você ver 'abc', "abc", ou """abc""", diga a si mesmo "Isso é uma sequência de 3 bytes correspondendo aos códigos ASCII para as letras a, b, c (tecnicamente, é UTF-8, mas ASCII e UTF-8 são os mesmos para letras latinas.)
2. Unicode são para strings
O segundo passo para começar a resolver o seu problema é começar a usar o tipo <'unicode'> como o seu contêiner para strings. Para iniciantes, isso significa usar o prefixo "u" para literais que criarão objetos objetos do tipo <'unicode'> ao invés de aspas regulares, que criarão objetos do tipo <'str'> (não se preocupe com docstrings; você raramente terá que manipulá-los, que é onde os problemas normalmente acontecem). Existem algumas boas práticas que discutirei abaixo.
3. UTF-8, UTF-16, e UTF-32 são formatos de serialização - NÃO Unicode
UTF-8 é uma codificação, da mesma forma que ASCII (mais sobre codificações abaixo), que são representados com bytes. A diferença é que a codificação UTF-8 pode representar cada caractere Unicode, enquanto que a codificação ASCII não pode. Mas, ambos ainda são bytes. Em oposição, um objeto do tipo <'unicode'> é apenas isso - um objeto Unicode. Ele não está codificado ou representado por qualquer sequência particular de bytes. Você pode pensar que objetos Unicode são como um armazenamento abstrato, representação Platônica de texto, enquanto que ASCII, UTF-8, UTF-16, etc. são diferentes maneiras de serializar (codificar) seu texto.
Certo, mas porque eu não posso usar str para strings? (Descrição detalhada do problema)
A razão para mudar da mentalidade acima é que desde que o tipo <'str'> armazena bytes, isso tem uma codificação implícita, e codificações (e/ou tentativas de decodificar uma codificação errada) causam a maioria dos problema de Unicode em Python 2.
O que quero dizer por codificação? É uma sequência de bits usados para representar os caracteres que nós lemos. Isso é, a string "abc" acima atualmente é armazenada dessa forma: 01100001 0100010 01100011.
Codificações são importantes porque você tem que usá-los sempre que o seu texto "viaja" para fora dos limites do seu programa - se você quer escrever uma string para um arquivo, ou mandar através da rede, ou armazenar em um banco de dados, isso precisa ter uma codificação. E se você mandar a codificação errada (isso é, uma sequência de bytes que o seu receptor não espera), você receberá erros de Unicode.
O problema com o tipo <'str'>, e a principal razão porque o Unicode em Python 2.7 ser confuso, é que a codificação de uma dada instância do tipo <'str'> é implícita. Isso significa que a única forma para descobrir a codificação de uma determinada instância do tipo <'str'> é tentar decodificar uma sequência de bytes, e ver se acontece algum erro. Infelizmente, há um monte de lugares onde uma sequência de bytes recebe uma decodificação invisível, que pode causar confusão e problemas.
Aqui estão alguns exemplos de linhas para demonstrar:
# Configura as variáveis que usaremos
>>> uni_greeting = u'Hi, my name is %s.'
>>> utf8_greeting = uni_greeting.encode('utf-8')
>>> uni_name = u'José' # Note o acento no e.
>>> utf8_name = uni_name.encode('utf-8')
# Conectar um Unicode em outro Unicode funciona bem
>>> uni_greeting % uni_name
u'Hi, my name is Jos\xe9.'
# Conectar UTF-8 em outra string UTF-8 funciona também
>>> utf8_greeting % utf8_name
'Hi, my name is Jos\xc3\xa9.'
# Você pode conectar Unicode em uma sequência de bytes UTF-8
# UTF-8 invisivelmente decodificada em Unicode;
# note o tipo de retorno
>>> utf8_greeting % uni_name
u'Hi, my name is Jos\xe9.'
# Mas, conectar uma string UTF-8 em um Unicode
# não funciona tão bem...
# Decodificação invisível não funciona nessa direção
>>> uni_greeting % utf8_name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte
0xc3 in position 3: ordinal not in range(128)
# Isso é, a menos que você conecte um dado compatível com ASCII
>>> uni_greeting % u'Bob'.encode('utf-8')
u'Hi, my name is Bob.'
# E você pode esquecer completamente a interpolação de
# strings se estiver usando UTF-16
>>> uni_greeting.encode('utf-16') % uni_name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: unsupported format character '' (0x0) at index 33
# Bem, você pode interpolar UTF-16 em UTF-8 porque esses
# são apenas sequências de bytes, mas é uma confusão inútil
>>> utf8_greeting % uni_name.encode('utf-16')
'Hi, my name is \xff\xfeJ\x00o\x00s\x00\xe9\x00.'
Os exemplos acima deveriam mostrar a você porque usar o tipo <'str'> é problemático; decodificações invisíveis com codificações implícitas para o tipo <'str'> podem esconder sérios problemas. Tudo funcionará bem enquanto o seu código tratar dados estritamente ASCII. Então, um dia, um infeliz "é" dará mancada na sua entrada de dados. Código implicitamente que assume (e decodificações invisíveis) entrada de dados codificados em ASCII, de repente, terão que lidar com dados codificados em UTF-8, e a coisa toda pode parar de funcionar; até mesmo seus manipuladores de exceção talvez comecem a jogar UnicodeDecodeErrors.
Solução: A 'câmara' Unicode
A melhor forma para atacar o problema, como muitas coisas em Python, é ser explicito. Isso significa que toda string que seu código trata precisa ser claramente tratada tanto como Unicode ou uma sequência de bytes.
A maneira mais sistemática para alcançar isso é fazer o seu código em um local limpo, somente com Unicode. Isso é, seu código deveria somente usar objetos Unicode internamente; você talvez até possa fazer verificações para o tipo <'str'> em lugares chaves para se assegurar.
Então, coloque 'câmaras' nos pontos de entrada para o seu código que irão assegurar que qualquer tentativa de entrada de uma sequência de bytes para seu código seja adequadamente vestida com uma roupa de coelho protetora para Unicode, antes de autorizar a sua entrada.
Por exemplo:
# RUIM -- dará bytes a você
with f = open('file.txt'):
...
# BOM -- dará a você Unicode
with f = codecs.open('file.txt', encoding='utf-8'):
...
Isso pode parecer lento e pesado, mas é atualmente muito fácil; as bibliotecas Python mais conhecidas seguem essa prática já, então você normalmente só precisa se preocupar com a entrada vinda de arquivos, pedidos de rede, etc.
Kit de construção para a câmara (Ferramentas Unicode Úteis)
Quase todos os problemas Unicode pode ser resolvidos através da correta aplicação dessa ferramentas; elas ajudarão você a construir uma câmara para manter o interior do seu código agradável e limpo:
- encode(): Você obtém de Unicode -> bytes
- decode(): Você obtém de bytes -> Unicode
- codecs.open(encoding="utf-8"): Lê e escreve arquivos diretamente para/a partir do Unicode (você pode usar qualquer codificação, não apenas utf-8, mas utf-8 é o mais comum).
- u"": Transforma suas strings literais em objetos Unicode, ao invés de uma sequência de bytes.
Solução de problemas
A chave para a solução de problemas de erros de Unicode em Python é conhecer os tipos de dados que você tem. Então, tente esses passos:
- Se algumas variáveis são sequências de bytes ao invés de objetos
Unicode, converta-os para objetos Unicode com decode() / u", antes de
manipulá-los.
>>> uni_greeting % utf8_name Traceback (most recent call last): File "
", line 1, in UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 3: ordinal not in range(128) # Solução: >>> uni_greeting % utf8_name.decode('utf-8') u'Hi, my name is Jos\xe9.' - Se todas as variáveis são sequências de bytes, provavelmente há uma incompatibilidade de codificação; converta qualquer coisa para objetos Unicode com decode() / u" e tente novamente.
- Se todas as variáveis já estão em Unicode, então talvez parte do seu
código talvez não saiba lidar com objetos Unicode, ou corrija o código, ou
codifique para uma sequência de bytes antes de enviar os dados (e assegure
de decodificar qualquer valor de retorno para Unicode):
>>> with open('test.out', 'wb') as f: >>> f.write(uni_name) Traceback (most recent call last): File "", line 1, in UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' in position 3: ordinal not in range(128) # Solução: >>> f.write(uni_name.encode('utf-8')) # Melhor solução: >>> with codecs.open('test.out', 'w', encoding='utf-8') as f: >>> f.write(uni_name)
Outros pontos
O Python 3 resolve esse problema tornando-se mais explícito: literais strings são Unicode por padrão, enquanto que sequência de bytes são armazenados em um novo tipo chamado 'byte'.
Para um olhar mais completo para essas questões, dê uma olhada em http://docs.python.org/2/howto/unicode.html
Boa sorte!
Essa publicação foi uma tradução livre de um artigo em inglês desse site: http://www.azavea.com/blogs/labs/2014/03/solving-unicode-problems-in-python-2-7/.
Algumas vezes é bem difícil captar exatamente o que o autor do artigo original expressou. Caso tenham alguma sugestão, por favor, especifiquem nos comentários.
Obrigado!
Nenhum comentário:
Postar um comentário