¿Algún problema al usar unicode_literals en Python 2.6?

101

Ya tenemos nuestro código base ejecutándose bajo Python 2.6. Para prepararnos para Python 3.0, comenzamos a agregar:

desde __future__ importar unicode_literals

en nuestros .pyarchivos (a medida que los modificamos). Me pregunto si alguien más ha estado haciendo esto y se ha encontrado con algún error no obvio (quizás después de pasar mucho tiempo depurando).

Jacob Gabrielson
fuente

Respuestas:

101

La principal fuente de problemas que he tenido al trabajar con cadenas Unicode es cuando se mezclan cadenas codificadas en utf-8 con cadenas Unicode.

Por ejemplo, considere los siguientes 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

El resultado de correr python one.pyes:

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)

En este ejemplo, two.namees una cadena codificada en utf-8 (no unicode) ya que no se importó unicode_literals, y one.namees una cadena unicode. Cuando mezcla ambos, Python intenta decodificar la cadena codificada (asumiendo que es ascii) y convertirla a Unicode y falla. Funcionaría si lo hicieras print name + two.name.decode('utf-8').

Lo mismo puede suceder si codifica una cadena e intenta mezclarlos más tarde. Por ejemplo, esto 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

Salida:

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

Pero después de agregar el import unicode_literalsNO:

# 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

Salida:

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)

Falla porque 'DEBUG: %s'es una cadena Unicode y, por lo tanto, Python intenta decodificar html. Un par de formas de arreglar la impresión son haciendo print str('DEBUG: %s') % htmlo print 'DEBUG: %s' % html.decode('utf-8').

Espero que esto le ayude a comprender las posibles trampas al usar cadenas Unicode.

Koba
fuente
11
Sugeriría ir con las decode()soluciones en lugar de las soluciones str()o encode(): cuanto más a menudo use objetos Unicode, más claro será el código, ya que lo que desea es manipular cadenas de caracteres, no matrices de bytes con una codificación implícita externamente.
Eric O Lebigot
8
Por favor, corrija su terminología. when you mix utf-8 encoded strings with unicode onesUTF-8 y Unicode no son 2 codificaciones diferentes; Unicode es un estándar y UTF-8 es una de las codificaciones que define.
Kos
11
@Kos: Creo que quiere decir mezclar "UTF-8 cadenas codificadas" objetos con Unicode (de ahí decodificado) objetos . El primero es de tipo str, el segundo es de tipo unicode. Al ser objetos diferentes, pueden surgir problemas si intenta
sumarlos
¿Esto se aplica a python>=2.6o python==2.6?
joar
16

También en 2.6 (antes de python 2.6.5 RC1 +), los literales Unicode no funcionan bien con los argumentos de palabras clave ( problema4978 ):

El siguiente código, por ejemplo, funciona sin unicode_literals, pero falla con TypeError: keywords must be stringsi se usa unicode_literals.

  >>> 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
mfazekas
fuente
17
Solo para su información, python 2.6.5 RC1 + ha solucionado esto.
Mahmoud Abdelkader
13

Descubrí que si agrega la unicode_literalsdirectiva, también debe agregar algo como:

 # -*- coding: utf-8

a la primera o segunda línea de su archivo .py. De lo contrario, líneas como:

 foo = "barré"

dar como resultado un error como:

SyntaxError: Carácter no ASCII '\ xc3' en el archivo mumble.py en la línea 198,
 pero sin codificación declarada; ver http://www.python.org/peps/pep-0263.html
 para detalles
Jacob Gabrielson
fuente
5
@IanMackinnon: Python 3 asume que los archivos son UTF8 por defecto
endolith
3
@endolith: ¡Pero Python 2 no lo hace, y dará el error de sintaxis si usa caracteres que no son ASCII incluso en los comentarios ! Entonces, en mi humilde opinión, # -*- coding: utf-8es una declaración prácticamente obligatoria, independientemente de si la usa unicode_literalso no
MestreLion
No -*-es obligatorio; si optara por el método compatible con emacs, creo que lo necesitaría -*- encoding: utf-8 -*-(consulte -*-también al final). Todo lo que necesitas es coding: utf-8(o incluso en =lugar de : ).
Chris Morgan
2
Obtiene este error, sea usted o no from __future__ import unicode_literals.
Flimm
3
La compatibilidad de Emacs requiere # -*- coding: utf-8 -*- "codificación" (no "codificación" o "codificación de archivos" o cualquier otra cosa; Python solo busca "codificación" independientemente de cualquier prefijo).
Alex Dupuy
7

También tenga en cuenta que unicode_literalafectará eval()pero no repr()(un comportamiento asimétrico que en mi humilde opinión es un error), es decir eval(repr(b'\xa4')), no será igual a b'\xa4'(como lo haría con Python 3).

Idealmente, el siguiente código sería invariante, que siempre debería funcionar, para todas las combinaciones unicode_literalsy el uso de Python {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+

La segunda afirmación funciona, ya que se repr('\xa4')evalúa u'\xa4'en Python 2.7.

hvr
fuente
2
Siento que el mayor problema aquí es que estás usando reprpara regenerar un objeto. La reprdocumentación establece claramente que esto no es un requisito. En mi opinión, esto lo relega repra algo útil solo para depurar.
jpmc26
5

Hay mas.

Hay bibliotecas y archivos incorporados que esperan cadenas que no toleran unicode.

Dos ejemplos:

incorporado:

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

(ligeramente esótico) no funciona con unicode_literals: type () espera una cadena.

biblioteca:

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

no funciona: la biblioteca wx pubsub espera un tipo de mensaje de cadena.

El primero es esotérico y se arregla fácilmente con

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

pero este último es devastador si su código está lleno de llamadas a pub.sendMessage () (que es el mío).

Maldita sea, ¿eh?!?

GreenAsJade
fuente
3
Y el material de tipo también se filtra en metaclases, por lo que en Django cualquier cadena que declare class Meta:debe serb'field_name'
Hamish Downer
2
Sí ... en mi caso me di cuenta de que valía la pena el esfuerzo de buscar y reemplazar todas las cadenas de sendMessage con versiones b '. Si desea evitar la temida excepción de "decodificación", no hay nada como usar estrictamente unicode en su programa, convertir en entrada y salida según sea necesario (el "sándwich unicode" mencionado en algún artículo que leí sobre el tema). En general, unicode_literals ha sido una gran victoria para mí ...
GreenAsJade