Des pièges à l'aide d'unicode_literals dans Python 2.6?

101

Nous avons déjà fait fonctionner notre base de code sous Python 2.6. Afin de préparer Python 3.0, nous avons commencé à ajouter:

de __future__ importer unicode_literals

dans nos .pyfichiers (au fur et à mesure que nous les modifions). Je me demande si quelqu'un d'autre a fait cela et a rencontré des pièges non évidents (peut-être après avoir passé beaucoup de temps à déboguer).

Jacob Gabrielson
la source

Réponses:

101

La principale source de problèmes que j'ai rencontrés en travaillant avec des chaînes Unicode est lorsque vous mélangez des chaînes encodées en utf-8 avec des chaînes Unicode.

Par exemple, considérez les scripts suivants.

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

Le résultat de l'exécution python one.pyest:

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)

Dans cet exemple, two.nameest une chaîne encodée en utf-8 (pas unicode) car elle n'a pas été importée unicode_literals, et one.nameest une chaîne unicode. Lorsque vous mélangez les deux, python essaie de décoder la chaîne codée (en supposant qu'elle est ascii) et de la convertir en Unicode et échoue. Cela fonctionnerait si vous le faisiez print name + two.name.decode('utf-8').

La même chose peut arriver si vous encodez une chaîne et essayez de les mélanger plus tard. Par exemple, cela fonctionne:

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

Production:

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

Mais après avoir ajouté le, import unicode_literalsil ne fait PAS:

# 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

Production:

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)

Il échoue car il 'DEBUG: %s's'agit d'une chaîne Unicode et par conséquent, python essaie de décoder html. Deux façons de corriger l'impression sont soit de faire print str('DEBUG: %s') % htmlou print 'DEBUG: %s' % html.decode('utf-8').

J'espère que cela vous aidera à comprendre les pièges potentiels lors de l'utilisation de chaînes Unicode.

Koba
la source
11
Je suggérerais d'aller avec les decode()solutions au lieu des solutions str()ou encode(): plus vous utilisez souvent des objets Unicode, plus le code est clair, car ce que vous voulez, c'est manipuler des chaînes de caractères, pas des tableaux d'octets avec un encodage implicite externe.
Eric O Lebigot
8
Veuillez corriger votre terminologie. when you mix utf-8 encoded strings with unicode onesUTF-8 et Unicode et pas 2 encodages différents; Unicode est un standard et UTF-8 est l'un des encodages qu'il définit.
Kos
11
@Kos: Je pense qu'il veut dire mélanger « utf-8 chaînes codées » objets avec unicode (donc décodé) des objets . Le premier est de type str, le second est de type unicode. Étant des objets différents, un problème peut survenir si vous essayez de les additionner / concaténer / interpoler
MestreLion
Cela s'applique-t-il à python>=2.6ou python==2.6?
joar
16

Aussi dans 2.6 (avant python 2.6.5 RC1 +) les littéraux unicode ne fonctionnent pas bien avec les arguments de mot-clé ( issue4978 ):

Le code suivant par exemple fonctionne sans unicode_literals, mais échoue avec TypeError: keywords must be stringsi unicode_literals est utilisé.

  >>> 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
la source
17
Juste pour info, python 2.6.5 RC1 + a corrigé ce problème.
Mahmoud Abdelkader
13

J'ai trouvé que si vous ajoutez la unicode_literalsdirective, vous devriez également ajouter quelque chose comme:

 # -*- coding: utf-8

à la première ou à la deuxième ligne de votre fichier .py. Sinon, des lignes telles que:

 foo = "barré"

entraîner une erreur telle que:

SyntaxError: Caractère non-ASCII '\ xc3' dans le fichier mumble.py à la ligne 198,
 mais aucun encodage déclaré; voir http://www.python.org/peps/pep-0263.html
 pour les détails
Jacob Gabrielson
la source
5
@IanMackinnon: Python 3 suppose que les fichiers sont UTF8 par défaut
endolith
3
@endolith: Mais Python 2 ne le fait pas, et il donnera l'erreur de syntaxe si vous utilisez des caractères non-ascii même dans les commentaires ! Donc, IMHO # -*- coding: utf-8est une déclaration pratiquement obligatoire, que vous l' unicode_literals
utilisiez
Le -*-n'est pas obligatoire; si vous optez pour la méthode compatible emacs, je pense que vous en auriez besoin -*- encoding: utf-8 -*-(voir aussi -*-à la fin). Tout ce dont vous avez besoin est coding: utf-8(ou même à la =place de : ).
Chris Morgan
2
Vous obtenez cette erreur que vous soyez ou non from __future__ import unicode_literals.
Flimm
3
La compatibilité avec Emacs nécessite le # -*- coding: utf-8 -*- "codage" (pas le "codage" ou le "codage de fichier" ou quoi que ce soit d'autre - Python recherche simplement le "codage" quel que soit le préfixe).
Alex Dupuy
7

Tenez également compte de ce unicode_literalqui affectera eval()mais pas repr()(un comportement asymétrique qui à mon avis est un bug), c'est-à-dire eval(repr(b'\xa4'))ne sera pas égal à b'\xa4'(comme ce serait le cas avec Python 3).

Idéalement, le code suivant serait un invariant, qui devrait toujours fonctionner, pour toutes les combinaisons d'utilisation 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+

La deuxième assertion fonctionne, puisqu'elle est repr('\xa4')évaluée u'\xa4'en Python 2.7.

hvr
la source
2
J'ai l'impression que le plus gros problème ici est que vous utilisez reprpour régénérer un objet. La reprdocumentation indique clairement que ce n'est pas une exigence. À mon avis, cela relègue reprà quelque chose d'utile uniquement pour le débogage.
jpmc26
5

Il y en a plus.

Il existe des bibliothèques et des fonctions intégrées qui attendent des chaînes qui ne tolèrent pas l'Unicode.

Deux exemples:

intégré:

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

(légèrement ésotique) ne fonctionne pas avec unicode_literals: type () attend une chaîne.

bibliothèque:

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

ne fonctionne pas: la bibliothèque wx pubsub attend un type de message string.

Le premier est ésotérique et se fixe facilement avec

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

mais ce dernier est dévastateur si votre code est plein d'appels à pub.sendMessage () (ce qui est le mien).

Dang, hein?!?

GreenAsJade
la source
3
Et le type de données fuit également dans les métaclasses - donc dans Django, toutes les chaînes que vous déclarez class Meta:devraient êtreb'field_name'
Hamish Downer
2
Ouais ... dans mon cas, j'ai réalisé que cela valait la peine de rechercher et de remplacer toutes les chaînes sendMessage par des versions b '. Si vous voulez éviter l'exception redoutée de "décodage", il n'y a rien de tel que d'utiliser strictement unicode dans votre programme, de convertir en entrée et en sortie si nécessaire (le "sandwich unicode" mentionné dans un article que j'ai lu sur le sujet). Dans l'ensemble, unicode_literals a été une grande victoire pour moi ...
GreenAsJade