Alguma pegadinha usando unicode_literals em Python 2.6?


101

Já colocamos nossa base de código em execução no Python 2.6. Para nos prepararmos para o Python 3.0, começamos a adicionar:

from __future__ import unicode_literals

em nossos .pyarquivos (conforme os modificamos). Estou me perguntando se mais alguém tem feito isso e se deparou com alguma pegadinha não óbvia (talvez depois de gastar muito tempo depurando).

Respostas:


101

A principal fonte de problemas que tive ao trabalhar com strings unicode é quando você mistura strings codificadas em utf-8 com strings Unicode.

Por exemplo, considere os seguintes scripts.

two.py

# encoding: utf-8
name = 'helló wörld from two'

one.py

# encoding: utf-8
from __future__ import unicode_literals
import two
name = 'helló wörld from one'
print name + two.name

O resultado da execução python one.pyé:

Traceback (most recent call last):
  File "one.py", line 5, in <module>
    print name + two.name
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 4: ordinal not in range(128)

Neste exemplo, two.nameé uma string codificada em utf-8 (não unicode), pois não foi importada unicode_literals, e one.nameé uma string unicode. Quando você mistura os dois, o python tenta decodificar a string codificada (assumindo que seja ascii) e convertê-la para Unicode e falha. Funcionaria se você fizesse print name + two.name.decode('utf-8').

A mesma coisa pode acontecer se você codificar uma string e tentar misturá-las mais tarde. Por exemplo, isso funciona:

# encoding: utf-8
html = '<html><body>helló wörld</body></html>'
if isinstance(html, unicode):
    html = html.encode('utf-8')
print 'DEBUG: %s' % html

Resultado:

DEBUG: <html><body>helló wörld</body></html>

Mas depois de adicionar import unicode_literalsNÃO:

# encoding: utf-8
from __future__ import unicode_literals
html = '<html><body>helló wörld</body></html>'
if isinstance(html, unicode):
    html = html.encode('utf-8')
print 'DEBUG: %s' % html

Resultado:

Traceback (most recent call last):
  File "test.py", line 6, in <module>
    print 'DEBUG: %s' % html
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 16: ordinal not in range(128)

Ele falha porque 'DEBUG: %s'é uma string Unicode e, portanto, o python tenta decodificar html. Algumas maneiras de corrigir a impressão são: print str('DEBUG: %s') % htmlou print 'DEBUG: %s' % html.decode('utf-8').

Espero que isso ajude você a entender as possíveis dificuldades ao usar strings Unicode.


11
Eu sugeriria ir com as decode()soluções em vez das soluções str()ou encode(): quanto mais você usar objetos Unicode, mais claro será o código, já que o que você deseja é manipular strings de caracteres, não matrizes de bytes com uma codificação implícita externamente.
Eric O Lebigot,

8
Por favor, corrija sua terminologia. when you mix utf-8 encoded strings with unicode onesUTF-8 e Unicode não são 2 codificações diferentes; Unicode é um padrão e UTF-8 é uma das codificações que ele define.
Kos

11
@Kos: Eu acho que ele significa misturar "utf-8 cordas codificados" objetos com unicode (daí decodificado) objetos . O primeiro é do tipo str, o último é do tipo unicode. Sendo objetos diferentes, o problema pode surgir se você tentar somar / concatenar / interpolar eles
MestreLion

Isso se aplica a python>=2.6ou python==2.6?
joar

16

Também no 2.6 (antes do python 2.6.5 RC1 +), os literais Unicode não funcionam bem com argumentos de palavra-chave (problema 4978 ):

O código a seguir, por exemplo, funciona sem unicode_literals, mas falha com TypeError: keywords must be stringse unicode_literals for usado.

  >>> def foo(a=None): pass
  ...
  >>> foo(**{'a':1})
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
      TypeError: foo() keywords must be strings

17
Apenas para sua informação, python 2.6.5 RC1 + corrigiu isso.
Mahmoud Abdelkader

13

Descobri que, se você adicionar a unicode_literalsdiretiva, também deve adicionar algo como:

 # -*- coding: utf-8

para a primeira ou segunda linha do arquivo .py. Caso contrário, linhas como:

 foo = "barré"

resultar em um erro como:

SyntaxError: Caractere não ASCII '\ xc3' no arquivo mumble.py na linha 198,
 mas nenhuma codificação declarada; consulte http://www.python.org/peps/pep-0263.html
 para detalhes

5
@IanMackinnon: Python 3 assume que os arquivos são UTF8 por padrão
endolith

3
@endolith: Mas o Python 2 não, e apresentará um erro de sintaxe se você usar caracteres não ASCII, mesmo em comentários ! Portanto, IMHO # -*- coding: utf-8é uma declaração virtualmente obrigatória, independentemente de você usar unicode_literalsou não
MestreLion

O -*-não é necessário; se você estivesse indo para o caminho compatível com emacs, acho que você precisaria -*- encoding: utf-8 -*-(veja o -*-no final também). Tudo que você precisa é coding: utf-8(ou mesmo em =vez de : ).
Chris Morgan

2
Você obtém este erro, seja você ou não from __future__ import unicode_literals.
Flimm de

3
A compatibilidade do Emacs requer # -*- coding: utf-8 -*- "codificação" (não "codificação" ou "codificação de arquivo" ou qualquer outra coisa - o Python apenas procura "codificação" independentemente de qualquer prefixo).
Alex Dupuy

7

Também leve em consideração que unicode_literalisso afetará, eval()mas não repr()(um comportamento assimétrico que imho é um bug), ou seja eval(repr(b'\xa4')), não será igual b'\xa4'(como seria com Python 3).

Idealmente, o código a seguir seria uma invariante, que sempre deve funcionar, para todas as combinações de uso de unicode_literalsPython {2.7, 3.x}:

from __future__ import unicode_literals

bstr = b'\xa4'
assert eval(repr(bstr)) == bstr # fails in Python 2.7, holds in 3.1+

ustr = '\xa4'
assert eval(repr(ustr)) == ustr # holds in Python 2.7 and 3.1+

A segunda afirmação funciona, uma vez que repr('\xa4')avalia u'\xa4'no Python 2.7.


2
Acho que o maior problema aqui é que você está usando reprpara regenerar um objeto. A reprdocumentação afirma claramente que isso não é um requisito. Na minha opinião, isso relega repra algo útil apenas para depuração.
jpmc26

5

Há mais.

Existem bibliotecas e builtins que esperam strings que não toleram Unicode.

Dois exemplos:

construídas em:

myenum = type('Enum', (), enum)

(ligeiramente esótico) não funciona com unicode_literals: type () espera uma string.

biblioteca:

from wx.lib.pubsub import pub
pub.sendMessage("LOG MESSAGE", msg="no go for unicode literals")

não funciona: a biblioteca wx pubsub espera um tipo de mensagem de string.

O primeiro é esotérico e facilmente corrigido com

myenum = type(b'Enum', (), enum)

mas o último é devastador se seu código estiver cheio de chamadas para pub.sendMessage () (que é o meu).

Droga, hein?!?


3
E o tipo de coisa também vaza em metaclasses - então, no Django, qualquer string que você declarar class Meta:deve serb'field_name'
Hamish Downer

2
Sim ... no meu caso percebi que valeu a pena procurar e substituir todas as strings sendMessage por versões b '. Se você quiser evitar a temida exceção de "decodificação", não há nada como usar estritamente unicode em seu programa, convertendo na entrada e na saída conforme necessário (o "sanduíche unicode" mencionado em algum artigo que li sobre o assunto). No geral, unicode_literals foi uma grande vitória para mim ...
GreenAsJade

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.