web-dev-qa-db-fra.com

Comment définir l'encodage sys.stdout dans Python 3?

Définir l'encodage de sortie par défaut dans Python 2 est un idiome bien connu:

sys.stdout = codecs.getwriter("utf-8")(sys.stdout)

Cela encapsule l'objet sys.stdout Dans un écrivain de codec qui code la sortie en UTF-8.

Cependant, cette technique ne fonctionne pas dans Python 3 car sys.stdout.write() attend un str, mais le résultat de l'encodage est bytes, et une erreur se produit lorsque codecs essaie d'écrire les octets codés dans le sys.stdout d'origine.

Quelle est la bonne façon de le faire dans Python 3?

53
Greg Hewgill

Depuis Python 3.7 vous pouvez changer l'encodage des flux standard avec reconfigure() :

sys.stdout.reconfigure(encoding='utf-8')

Vous pouvez également modifier la façon dont les erreurs de codage sont gérées en ajoutant un paramètre errors.

15
sth

Python 3.1 a ajouté io.TextIOBase.detach(), avec une note dans la documentation pour sys.stdout :

Les flux standard sont en mode texte par défaut. Pour y écrire ou lire des données binaires, utilisez le tampon binaire sous-jacent. Par exemple, pour écrire des octets dans stdout, utilisez sys.stdout.buffer.write(b'abc'). L'utilisation de io.TextIOBase.detach() streams peut être rendue binaire par défaut. Cette fonction définit stdin et stdout sur binaire:

def make_streams_binary():
    sys.stdin = sys.stdin.detach()
    sys.stdout = sys.stdout.detach()

Par conséquent, l'idiome correspondant pour Python 3.1 et versions ultérieures est:

sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())
37
Greg Hewgill

J'ai trouvé ce fil en recherchant des solutions à la même erreur,

Une solution alternative à celles déjà suggérées consiste à définir la variable d'environnement PYTHONIOENCODINGavant Python démarre, pour mon usage - cela pose moins de problèmes que l'échange sys.stdout après Python est initialisé:

PYTHONIOENCODING=utf-8:surrogateescape python3 somescript.py

Avec l'avantage de ne pas avoir à aller éditer le code Python.

28
ideasman42

D'autres réponses semblent recommander l'utilisation de codecs, mais open fonctionne pour moi:

import sys
sys.stdout = open(sys.stdout.fileno(), mode='w', encoding='utf8', buffering=1)
print("日本語")
# Also works with other methods of writing to stdout:
sys.stdout.write("日本語\n")
sys.stdout.buffer.write("日本語\n".encode())

Cela fonctionne même lorsque je l'exécute avec PYTHONIOENCODING="ascii".

26
Jack O'Connor

Définir l'encodage de sortie par défaut dans Python 2 est un idiome bien connu

Eek! Est-ce un idiome bien connu dans Python 2? Cela me semble une erreur dangereuse.

Cela gâchera certainement tout script qui essaie d'écrire du binaire sur stdout (dont vous aurez besoin si vous êtes un script CGI renvoyant une image, par exemple). Les octets et les caractères sont des animaux très différents; ce n'est pas une bonne idée de patcher une interface qui est spécifiée pour accepter des octets avec une qui ne prend que des caractères.

CGI et HTTP en général fonctionnent explicitement avec des octets. Vous ne devez envoyer des octets qu'à sys.stdout. Dans Python 3 cela signifie utiliser sys.stdout.buffer.write pour envoyer des octets directement. L'encodage du contenu de la page pour correspondre à son paramètre charset doit être géré à un niveau supérieur dans votre application (dans les cas où vous renvoyez du contenu textuel plutôt que binaire). Cela signifie également que print n'est plus bon pour CGI.

(Pour ajouter à la confusion, le CGIHandler de wsgiref a été cassé dans py3k jusqu'à très récemment, ce qui rend impossible le déploiement de WSGI sur CGI de cette façon. Avec PEP 3333 et Python 3.2, cela est enfin réalisable.)

17
bobince

L'utilisation de detach() fait que l'interpréteur affiche un avertissement lorsqu'il essaie de fermer stdout juste avant de quitter:

Exception ignored in: <_io.TextIOWrapper mode='w' encoding='UTF-8'>
ValueError: underlying buffer has been detached

Au lieu de cela, cela a bien fonctionné pour moi:

default_out = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

(Et, bien sûr, en écrivant à default_out au lieu de stdout.)

10
ptomato

sys.stdout est en mode texte dans Python 3. Par conséquent, vous y écrivez directement unicode, et l'idiome pour Python 2 n'est plus nécessaire).

Où cela échouerait dans Python 2:

>>> import sys
>>> sys.stdout.write(u"ûnicöde")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xfb' in position 0: ordinal not in range(128)

Cependant, cela fonctionne simplement dandy dans Python 3:

>>> import sys
>>> sys.stdout.write("Ûnicöde")
Ûnicöde7

Maintenant, si votre Python ne sait pas ce que votre codage stdouts est réellement, c'est un problème différent, très probablement dans la construction de Python.

8
Lennart Regebro