Pourquoi ne devrions-nous PAS utiliser sys.setdefaultencoding («utf-8») dans un script py?


166

J'ai vu quelques scripts py qui l'utilisent en haut du script. Dans quels cas faut-il l'utiliser?

import sys
reload(sys)
sys.setdefaultencoding("utf-8")

2
il y a un problème avec l'utilisation de ceci dans ipython,% time cesse de fonctionner github.com/ipython/ipython/issues/8071
seanv507

3
@ seanv507, lisez les réponses - il est fortement déconseillé de l'utiliser
Alastair McCormack


2
Comment n'est-ce pas une copie exacte des dangers de sys.setdefaultencoding ('utf-8') ? Bien que cette (2010) demande soit antérieure à celle-là (2015)? Mais cette demande a aussi de bonnes réponses. Que faire? De plus, pour être clair, cette question n'a de sens que sur Python 2 et non sur 3, mais ce n'est nulle part étiqueté ou mentionné.
smci

à lire avant de plonger dans les réponses SO: pythonhosted.org/kitchen/unicode-frustrations.html
ccpizza

Réponses:


141

Selon la documentation: Cela vous permet de passer de l'ASCII par défaut à d'autres encodages tels que UTF-8, que le runtime Python utilisera chaque fois qu'il doit décoder un tampon de chaîne en unicode.

Cette fonction n'est disponible qu'au démarrage de Python, lorsque Python analyse l'environnement. Il doit être appelé dans un module à l'échelle du système,, Une sitecustomize.pyfois ce module évalué, la setdefaultencoding()fonction est supprimée du sysmodule.

La seule façon de l'utiliser est avec un hack de rechargement qui ramène l'attribut.

De plus, l'utilisation de sys.setdefaultencoding()a toujours été découragée , et elle est devenue un no-op dans py3k. L'encodage de py3k est câblé à "utf-8" et sa modification génère une erreur.

Je suggère quelques conseils pour lire:


6
Super truc, bien qu'il y ait un peu de mort par trop d'informations ici. J'ai appris le plus simplement en me concentrant sur cet article: blog.notdot.net/2010/07/Getting-unicode-right-in-Python
mbb

3
Je voudrais ajouter que l'encodage par défaut est également utilisé pour l' encodage (lors de l'écriture sys.stdoutlorsqu'il a un Noneencodage, comme lors de la redirection de la sortie d'un programme Python).
Eric O Lebigot

14
+1 pour "l'utilisation de sys.setdefaultencoding()a toujours été découragée"
jfs

7
«câblé à utf-8» n'est pas vrai, ce n'est pas câblé et ce n'est pas toujours le cas UTF-8. LC_ALL=en_US.UTF-8 python3 -c 'import sys; print(sys.stdout.encoding)'donne UTF-8mais LC_ALL=C python3 -c 'import sys; print(sys.stdout.encoding)'donneANSI_X3.4-1968 (ou peut-être autre chose)
Tino

7
@Tino, l'encodage de la console est distinct de l'encodage par défaut.
Alastair McCormack

59

tl; dr

La réponse est JAMAIS ! (sauf si vous savez vraiment ce que vous faites)

9/10 fois la solution peut être résolue avec une bonne compréhension de l'encodage / décodage.

1/10 des personnes ont une langue ou un environnement mal défini et doivent définir:

PYTHONIOENCODING="UTF-8"  

dans leur environnement pour résoudre les problèmes d'impression de la console.

Qu'est ce que ça fait?

sys.setdefaultencoding("utf-8")(barré pour éviter la réutilisation) modifie l'encodage / décodage par défaut utilisé chaque fois que Python 2.x a besoin de convertir un Unicode () en str () (et vice-versa) et l'encodage n'est pas donné. C'est à dire:

str(u"\u20AC")
unicode("€")
"{}".format(u"\u20AC") 

Dans Python 2.x, l'encodage par défaut est défini sur ASCII et les exemples ci-dessus échoueront avec:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 0: ordinal not in range(128)

(Ma console est configurée en UTF-8, donc "€" = '\xe2\x82\xac' , exception sur \xe2)

ou

UnicodeEncodeError: 'ascii' codec can't encode character u'\u20ac' in position 0: ordinal not in range(128)

sys.setdefaultencoding("utf-8")permettra à ceux-ci de travailler pour moi , mais ne fonctionnera pas nécessairement pour les personnes qui n'utilisent pas UTF-8. La valeur par défaut de ASCII garantit que les hypothèses de codage ne sont pas intégrées dans le code

Console

sys.setdefaultencoding("utf-8")a également pour effet secondaire de sembler corriger sys.stdout.encoding, utilisé lors de l'impression de caractères sur la console. Python utilise les paramètres régionaux de l'utilisateur (Linux / OS X / Un * x) ou la page de codes (Windows) pour définir cela. Parfois, les paramètres régionaux d'un utilisateur sont défectueux et nécessitent simplementPYTHONIOENCODING de corriger le codage de la console .

Exemple:

$ export LANG=en_GB.gibberish
$ python
>>> import sys
>>> sys.stdout.encoding
'ANSI_X3.4-1968'
>>> print u"\u20AC"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u20ac' in position 0: ordinal not in range(128)
>>> exit()

$ PYTHONIOENCODING=UTF-8 python
>>> import sys
>>> sys.stdout.encoding
'UTF-8'
>>> print u"\u20AC"
€

Quel est le problème avec problème sys.setdefaultencoding ("utf-8") ?

Les gens développent contre Python 2.x depuis 16 ans, sachant que l'encodage par défaut est ASCII. UnicodeErrorDes méthodes de gestion des exceptions ont été écrites pour gérer les conversions chaîne en Unicode sur les chaînes qui contiennent des caractères non ASCII.

De https://anonbadger.wordpress.com/2015/06/16/why-sys-setdefaultencoding-will-break-code/

def welcome_message(byte_string):
    try:
        return u"%s runs your business" % byte_string
    except UnicodeError:
        return u"%s runs your business" % unicode(byte_string,
            encoding=detect_encoding(byte_string))

print(welcome_message(u"Angstrom (Å®)".encode("latin-1"))

Avant de définir le codage par défaut, ce code ne pouvait pas décoder le «Å» dans le codage ascii et entrerait ensuite dans le gestionnaire d'exceptions pour deviner le codage et le transformer correctement en unicode. Impression: Angstrom (Å®) dirige votre entreprise. Une fois que vous avez défini le codage par défaut sur utf-8, le code trouvera que byte_string peut être interprété comme utf-8 et ainsi il modifiera les données et retournera ceci à la place: Angstrom (Ů) dirige votre entreprise.

Changer ce qui devrait être une constante aura des effets dramatiques sur les modules dont vous dépendez. Il est préférable de simplement corriger les données entrant et sortant de votre code.

Exemple de problème

Bien que le paramètre de codage par défaut en UTF-8 ne soit pas la cause principale dans l'exemple suivant, il montre comment les problèmes sont masqués et comment, lorsque le codage d'entrée change, le code se brise de manière non évidente: UnicodeDecodeError: le codec 'utf8' peut 't décoder l'octet 0x80 en position 3131: octet de départ invalide


2
Bien qu'il y ait des surprises sys.setdefaultencoding("utf-8"), il est bon de faire en sorte que le code se comporte davantage comme Python 3. Nous sommes en 2017 maintenant. Même lorsque vous avez écrit la réponse en 2015, je pense qu'il valait déjà mieux regarder en avant plutôt qu'en arrière. C'était en fait la solution la plus simple pour moi, quand j'ai trouvé que mon code se comportait différemment dans Python 2 selon que la sortie était redirigée (très mauvais problème pour Python 2). Inutile de dire que je l'ai déjà fait # coding: utf-8et que je n'ai pas besoin de solutions de contournement pour Python 3 (je dois en fait masquer la setdefaultencodingvérification de la version).
Yongwei Wu

C'est génial et cela fonctionne pour vous mais sys.setdefaultencoding("utf-8")ne rend pas votre code Py 2.x compatible avec Python 3. Il ne corrige pas non plus les modules externes qui supposent que l'encodage par défaut est ASCII. Rendre votre code compatible Python 3 est très simple et ne nécessite pas ce méchant hack. Par exemple, pourquoi cela pose des problèmes très réels, voir mon expérience avec Amazon qui joue avec cette hypothèse: stackoverflow.com/questions/39465220/…
Alastair McCormack

1
@AlastairMcCormack you rock, mon site existe depuis des mois et je ne savais pas quoi faire. Enfin, PYTHONIOENCODING="UTF-8"j'ai aidé mon environnement Python2.7 Django-1.11. Merci.
sam

Je sais que vous avez copié l'exemple, mais je peux trouver ce que contient le package detect_encoding.
dlamblin

@dlamblin L'exemple de code est pour prouver la citation et n'est pas censé être utilisé dans votre code. Imaginez que ce detect_encodingsoit une méthode qui pourrait détecter le codage d'une chaîne en fonction d'indices de langage.
Alastair McCormack

18
#!/usr/bin/env python
#-*- coding: utf-8 -*-
u = u'moçambique'
print u.encode("utf-8")
print u

chmod +x test.py
./test.py
moçambique
moçambique

./test.py > output.txt
Traceback (most recent call last):
  File "./test.py", line 5, in <module>
    print u
UnicodeEncodeError: 'ascii' codec can't encode character 
u'\xe7' in position 2: ordinal not in range(128)

sur le shell fonctionne, l'envoi à sdtout non, donc c'est une solution de contournement, pour écrire sur stdout.

J'ai fait une autre approche, qui n'est pas exécutée si sys.stdout.encoding n'est pas défini, ou en d'autres termes, il faut d'abord exporter PYTHONIOENCODING = UTF-8 pour écrire dans stdout.

import sys
if (sys.stdout.encoding is None):            
    print >> sys.stderr, "please set python env PYTHONIOENCODING=UTF-8, example: export PYTHONIOENCODING=UTF-8, when write to stdout." 
    exit(1)


donc, en utilisant le même exemple:

export PYTHONIOENCODING=UTF-8
./test.py > output.txt

marchera


3
Cela ne répond pas à la question posée. Plutôt quelques réflexions tangentielles sur le sujet.
ivan_pozdeev

3
  • Le premier danger réside dans reload(sys).

    Lorsque vous rechargez un module, vous obtenez en fait deux copies du module dans votre runtime. L'ancien module est un objet Python comme tout le reste, et reste en vie tant qu'il y a des références. Ainsi, la moitié des objets pointeront vers l'ancien module et l'autre moitié vers le nouveau. Lorsque vous apportez un changement, vous ne le verrez jamais venir lorsqu'un objet aléatoire ne voit pas le changement:

    (This is IPython shell)
    
    In [1]: import sys
    
    In [2]: sys.stdout
    Out[2]: <colorama.ansitowin32.StreamWrapper at 0x3a2aac8>
    
    In [3]: reload(sys)
    <module 'sys' (built-in)>
    
    In [4]: sys.stdout
    Out[4]: <open file '<stdout>', mode 'w' at 0x00000000022E20C0>
    
    In [11]: import IPython.terminal
    
    In [14]: IPython.terminal.interactiveshell.sys.stdout
    Out[14]: <colorama.ansitowin32.StreamWrapper at 0x3a9aac8>
  • Maintenant, sys.setdefaultencoding()bon

    Tout ce que cela affecte, c'est la conversion implicitestr<->unicode . Maintenant, utf-8est le codage le plus sain sur la planète (rétrocompatible avec ASCII et tout), la conversion maintenant "fonctionne juste", qu'est - ce qui pourrait éventuellement mal tourner?

    Eh bien, n'importe quoi. Et c'est le danger.

    • Il peut y avoir du code qui repose sur le UnicodeErrorlancement pour une entrée non ASCII, ou qui effectue le transcodage avec un gestionnaire d'erreurs, ce qui produit maintenant un résultat inattendu. Et comme tout le code est testé avec le paramètre par défaut, vous êtes strictement sur le territoire "non pris en charge" ici , et personne ne vous donne de garanties sur le comportement de leur code.
    • Le transcodage peut produire des résultats inattendus ou inutilisables si tout sur le système n'utilise pas UTF-8 car Python 2 a en fait plusieurs «encodages de chaîne par défaut» indépendants . (N'oubliez pas qu'un programme doit fonctionner pour le client, sur l'équipement du client.)
      • Encore une fois, le pire est que vous ne le saurez jamais, car la conversion est implicite - vous ne savez pas vraiment quand et où cela se produit. (Python Zen, koan 2 ahoy!) Vous ne saurez jamais pourquoi (et si) votre code fonctionne sur un système et se brise sur un autre. (Ou mieux encore, fonctionne dans l'IDE et se brise dans la console.)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.