2010-12-07 67 views
35

Réglage du codage de sortie par défaut en Python 2 est un langage bien connu:Comment définir l'encodage sys.stdout dans Python 3?

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

Ce encapsule l'objet sys.stdout dans un écrivain codec qui code la sortie en UTF-8.

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

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

+0

2to3 est un outil utile pour des questions comme celles-ci. –

+0

@dan_waterworth: Je n'avais pas pensé à essayer ça avant, mais j'ai juste essayé '2to3' maintenant et cela n'a suggéré aucun changement pour le code donné. –

+3

Si le nouveau code ne fonctionne pas alors je vous suggère d'ajouter cela comme un bug. –

Répondre

30

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

Les flux standards sont en mode texte par défaut. Pour écrire ou lire des données binaires, utilisez le tampon binaire sous-jacent. Par exemple, pour écrire des octets à stdout, utilisez sys.stdout.buffer.write(b'abc'). En utilisant io.TextIOBase.detach() les flux peuvent être rendus binaires par défaut. Cette fonction définit stdin et stdout 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 version ultérieure est:

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

Je voudrais utiliser «PYTHONIOENCODING»; sinon 'io.TextIOWrapper' pourrait être une meilleure alternative que' codecs' pour gérer correctement les nouvelles lignes. – jfs

+0

Cela modifie totalement le comportement de 'sys.stdout'. Le 'StreamWriter' retourné par' codecs.getwriter' n'est plus tamponné en ligne, par exemple – Sebastian

7

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

Lorsque cela échouerait en 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, il fonctionne dandy en Python 3:

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

Maintenant, si votre Python ne sait pas ce que votre stdouts encodage est en fait , c'est un problème différent, très probablement dans la construction du Python.

+2

Mon contexte exécutait le script Python en tant que CGI sous Apache, où le codage de sortie par défaut n'était pas ce dont j'avais besoin (j'avais besoin de UTF- 8). Je pense qu'il est préférable pour le script de s'assurer que sa sortie est dans le codage correct, plutôt que de s'appuyer sur des paramètres externes (tels que des variables d'environnement comme PYTHONIOENCODING). –

+1

Encore une preuve que l'utilisation de stdout pour la communication de processus est une grosse erreur. Je réalise que vous n'avez peut-être pas d'autre choix que d'utiliser CGI dans ce cas, alors ce n'est pas de votre faute. :-) –

+0

S'il est vrai que 'sys.stdout' est un fichier * binaire * dans Python 2 et un fichier * text * dans Python 3, je pense que votre exemple Python 2 échoue car la chaîne unicode' u 'ûnicöde "' qui est implicitement codé dans la méthode 'sys.stdout.write' a des caractères en dehors de la plage ASCII. Si vous modifiez vos variables d'environnement 'LC_CTYPE',' LANG' ou 'PYTHONIOENCODING' à un encodage qui a tous les caractères dans la chaîne unicode, vous ne devriez pas avoir d'erreur. (J'ai essayé sur Python 2.7.) – Maggyero

16

Réglage du codage de sortie par défaut en Python 2 est un langage bien connu

Eek! Est-ce un idiome bien connu dans Python 2? Cela ressemble à une erreur dangereuse pour moi.

Il va certainement gâcher n'importe quel script qui essaye d'écrire binaire sur stdout (ce dont vous aurez besoin si vous êtes un script CGI retournant 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 corriger une interface qui est spécifiée pour accepter des octets avec un seul qui ne prend que des caractères. En règle générale, CGI et HTTP fonctionnent explicitement avec des octets en utilisant les octets

Vous devriez seulement envoyer des octets à sys.stdout. En Python 3, cela signifie utiliser sys.stdout.buffer.write pour envoyer des octets directement. Le contenu de la page d'encodage correspondant à son paramètre charset doit être traité à un niveau plus élevé dans votre application (dans les cas où vous renvoyez du contenu textuel plutôt que binaire). Cela signifie aussi que print n'est plus bon pour CGI.

(Pour ajouter à la confusion, CGIHandler de wsgiref a été brisé en Py3K jusqu'à très récemment, ce qui rend impossible de déployer WSGI à CGI de cette façon. Avec PEP 3333 et Python 3.2 est enfin réalisable.)

+0

Ce commentaire doit être mis à jour, concernant la version 3.3 et 3.4 de Python à venir. Merci – soshial

18

I trouvé ce fil lors de la recherche de solutions à la même erreur,

Une solution alternative à ceux déjà suggéré est de définir l'PYTHONIOENCODING variable d'environnement avant Python commence, pour mon utilisation - ce qui est moins dérangerais é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.

+0

Thumbs-upping principalement parce que PYTHONIOENCODING = utf-8 résolu mon problème, après de nombreuses heures de recherche. – theeggman85

5

En utilisant detach() l'interpréteur d'imprimer un avertissement quand il essaie de fermer stdout juste avant sa sortie:

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.)

15

Les autres réponses semblent recommander d'utiliser 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 quand je le lance avec PYTHONIOENCODING="ascii".