Festlegen der richtigen Codierung beim Weiterleiten von stdout in Python

343

Wenn die Ausgabe eines Python-Programms weitergeleitet wird, ist der Python-Interpreter verwirrt über die Codierung und setzt sie auf Keine. Dies bedeutet ein Programm wie dieses:

# -*- coding: utf-8 -*-
print u"åäö"

funktioniert bei normaler Ausführung einwandfrei, schlägt jedoch fehl mit:

UnicodeEncodeError: Der Codec 'ascii' kann das Zeichen u '\ xa0' an Position 0 nicht codieren: Ordnungszahl nicht im Bereich (128)

bei Verwendung in einer Rohrfolge.

Was ist der beste Weg, um diese Arbeit beim Verrohren zu machen? Kann ich ihm einfach sagen, dass er die Codierung verwenden soll, die die Shell / das Dateisystem / was auch immer verwendet?

Die Vorschläge, die ich bisher gesehen habe, sind, Ihre site.py direkt zu ändern oder die Defaultencodierung mit diesem Hack fest zu codieren:

# -*- coding: utf-8 -*-
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
print u"åäö"

Gibt es einen besseren Weg, um Rohrleitungen zum Laufen zu bringen?

Joakim Lundborg
quelle
1
Siehe auch stackoverflow.com/questions/4545661/…
ShreevatsaR
2
Wenn Sie dieses Problem unter Windows haben, können Sie es auch ausführen, chcp 65001bevor Sie Ihr Skript ausführen. Dies kann Probleme haben, hilft aber oft und erfordert nicht viel Tippen (weniger als set PYTHONIOENCODING=utf_8).
Tomasz Gandor
Der Befehl chcp ist nicht dasselbe wie das Einstellen von PYTHONIOENCODING. Ich denke, chcp ist nur eine Konfiguration für das Terminal selbst und hat nichts mit dem Schreiben in eine Datei zu tun (was Sie tun, wenn Sie stdout weiterleiten). Versuchen Sie setx PYTHONENCODING utf-8, es dauerhaft zu machen, wenn Sie die Eingabe speichern möchten.
Ejm
Ich hatte ein ähnliches Problem und fand hier eine Lösung -> stackoverflow.com/questions/48782529/…
bkrishna2006

Antworten:

162

Ihr Code funktioniert, wenn er in einem Skript ausgeführt wird, da Python die Ausgabe in die von Ihrer Terminalanwendung verwendete Codierung codiert. Wenn Sie Rohrleitungen verwenden, müssen Sie diese selbst codieren.

Als Faustregel gilt: Verwenden Sie Unicode immer intern. Dekodieren Sie, was Sie empfangen, und kodieren Sie, was Sie senden.

# -*- coding: utf-8 -*-
print u"åäö".encode('utf-8')

Ein weiteres didaktisches Beispiel ist ein Python-Programm zum Konvertieren zwischen ISO-8859-1 und UTF-8, bei dem alles dazwischen in Großbuchstaben geschrieben wird.

import sys
for line in sys.stdin:
    # Decode what you receive:
    line = line.decode('iso8859-1')

    # Work with Unicode internally:
    line = line.upper()

    # Encode what you send:
    line = line.encode('utf-8')
    sys.stdout.write(line)

Das Festlegen der Standardcodierung des Systems ist eine schlechte Idee, da einige von Ihnen verwendete Module und Bibliotheken sich darauf verlassen können, dass es sich um ASCII handelt. Tu es nicht.

nosklo
quelle
11
Das Problem ist, dass der Benutzer die Codierung nicht explizit angeben möchte. Er möchte nur Unicode für IO verwenden. Die von ihm verwendete Codierung sollte eine Codierung sein, die in den Ländereinstellungen angegeben ist, nicht in den Einstellungen der Terminalanwendung. AFAIK, Python 3 verwendet in diesem Fall eine Gebietsschema- Codierung. Ändern sys.stdoutscheint ein angenehmerer Weg zu sein.
Andrey Vlasovskikh
4
Das genaue Codieren / Decodieren jeder Zeichenfolge kann zu Fehlern führen, wenn ein Codierungs- oder Decodierungsaufruf fehlt oder irgendwo zu viel hinzugefügt wird. Die Ausgabecodierung kann eingestellt werden, wenn die Ausgabe ein Terminal ist, also kann sie eingestellt werden, wenn die Ausgabe kein Terminal ist. Es gibt sogar eine Standard-LC_CTYPE-Umgebung, um dies anzugeben. Es ist ein aber in Python, dass es dies nicht respektiert.
Rasmus Kaj
65
Diese Antwort ist falsch. Sie sollten nicht jede Eingabe und Ausgabe Ihres Programms manuell konvertieren. das ist spröde und völlig unhaltbar.
Glenn Maynard
29
@Glenn Maynard: Also, was ist IYO die richtige Antwort? Es ist hilfreicher, uns zu sagen, als nur zu sagen: "Diese Antwort ist falsch"
smci
14
@smci: Die Antwort ist, ändern Sie nicht Ihr Skript, setzen PYTHONIOENCODINGSie , wenn Sie die Standardausgabe des Skripts in Python 2 umleiten.
jfs
168

Zunächst zu dieser Lösung:

# -*- coding: utf-8 -*-
print u"åäö".encode('utf-8')

Es ist nicht praktisch, jedes Mal explizit mit einer bestimmten Codierung zu drucken. Das wäre repetitiv und fehleranfällig.

Eine bessere Lösung besteht darin, sys.stdoutzu Beginn Ihres Programms zu ändern und mit einer ausgewählten Codierung zu codieren. Hier ist eine Lösung, die ich in Python gefunden habe: Wie wird sys.stdout.encoding ausgewählt? , insbesondere ein Kommentar von "toka":

import sys
import codecs
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
Craig McQueen
quelle
7
Wenn Sie sys.stdout so ändern, dass nur Unicode akzeptiert wird, werden leider viele Bibliotheken beschädigt, die erwarten, dass codierte Bytestrings akzeptiert werden.
Nosklo
6
nosklo: Wie kann es dann zuverlässig und automatisch funktionieren, wenn die Ausgabe ein Terminal ist?
Rasmus Kaj
3
@Rasmus Kaj: Definieren Sie einfach Ihre eigene Unicode-Druckfunktion und verwenden Sie sie jedes Mal, wenn Sie Unicode drucken möchten: def myprint(unicodeobj): print unicodeobj.encode('utf-8')- Sie erkennen die Terminalcodierung automatisch durch Inspektion sys.stdout.encoding, sollten jedoch den Fall berücksichtigen, in dem sie sich befindet None(dh wenn Sie die Ausgabe in eine Datei umleiten). Sie benötigen also ohnehin eine separate Funktion.
Nosklo
3
@nosklo: Dadurch akzeptiert sys.stdout nicht nur Unicode. Sie können sowohl str als auch unicode an einen StreamWriter übergeben.
Glenn Maynard
9
Ich gehe davon aus, dass diese Antwort für Python2 gedacht war. Seien Sie vorsichtig mit diesem Code, der sowohl Python2 als auch Python3 unterstützen soll . Für mich ist es kaputt, wenn es unter python3 läuft.
wim
130

Möglicherweise möchten Sie versuchen, die Umgebungsvariable "PYTHONIOENCODING" in "utf_8" zu ändern. Ich habe eine Seite über meine Tortur mit diesem Problem geschrieben .

Tl; dr des Blogposts:

import sys, locale, os
print(sys.stdout.encoding)
print(sys.stdout.isatty())
print(locale.getpreferredencoding())
print(sys.getfilesystemencoding())
print(os.environ["PYTHONIOENCODING"])
print(chr(246), chr(9786), chr(9787))

gibt Ihnen

utf_8
False
ANSI_X3.4-1968
ascii
utf_8
ö ☺ ☻
Daveagp
quelle
2
Das Ändern von sys.stdout.encoding funktioniert möglicherweise nicht, aber das Ändern von sys.stdout funktioniert : sys.stdout = codecs.getwriter(encoding)(sys.stdout). Dies kann innerhalb des Python-Programms erfolgen, sodass der Benutzer nicht gezwungen ist, eine env-Variable festzulegen.
blueFast
7
@ jeckyll2hide: PYTHONIOENCODINGfunktioniert. Wie Bytes als Text interpretiert werden, wird von der Benutzerumgebung definiert . Ihr Skript sollte nicht davon ausgehen und der Benutzerumgebung vorschreiben, welche Zeichenkodierung verwendet werden soll. Wenn Python die Einstellungen nicht automatisch übernimmt, PYTHONIOENCODINGkann dies für Ihr Skript festgelegt werden. Sie sollten es nicht benötigen, es sei denn, die Ausgabe wird in eine Datei / Pipe umgeleitet.
JFS
8
+1. Ehrlich gesagt denke ich, dass es ein Python-Fehler ist. Wenn ich die Ausgabe umleiten möchte, möchte ich dieselben Bytes wie auf dem Terminal, jedoch in einer Datei. Vielleicht ist es nicht jedermanns Sache, aber es ist ein guter Standard. Ein harter Absturz ohne Erklärung für eine triviale Operation, die normalerweise "nur funktioniert", ist eine schlechte Standardeinstellung.
SnakE
@SnakE: Die einzige Möglichkeit, zu erklären, warum die Implementierung von Python absichtlich eine eiserne und dauerhafte Wahl der Codierung auf stdout zum Startzeitpunkt erzwingen würde, könnte darin bestehen, zu verhindern, dass später schlecht codiertes Material herauskommt. Oder das Ändern ist nur eine nicht implementierte Funktion. In diesem Fall wäre es eine vernünftige Anforderung an die Python-Funktion, dem Benutzer zu erlauben, sie später zu ändern.
Daveagp
2
@daveagp Mein Punkt ist, dass das Verhalten meines Programms nicht davon abhängen sollte, ob es umgeleitet wird oder nicht - es sei denn, ich möchte es wirklich. In diesem Fall implementiere ich es selbst. Python verhält sich entgegen meiner Erfahrung mit anderen Konsolentools. Dies verstößt gegen das Prinzip der geringsten Überraschung. Ich halte dies für einen Konstruktionsfehler, es sei denn, es gibt eine sehr starke Begründung.
SnakE
62
export PYTHONIOENCODING=utf-8

mach den Job, kann ihn aber nicht auf Python selbst einstellen ...

Was wir tun können, ist zu überprüfen, ob es nicht eingestellt ist, und den Benutzer anzuweisen, es vor dem Aufruf des Skripts festzulegen mit:

if __name__ == '__main__':
    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)

Update, um auf den Kommentar zu antworten: Das Problem besteht nur beim Weiterleiten an stdout. Ich habe in Fedora 25 Python 2.7.13 getestet

python --version
Python 2.7.13

Katze b.py.

#!/usr/bin/env python
#-*- coding: utf-8 -*-
import sys

print sys.stdout.encoding

läuft ./b.py

UTF-8

läuft ./b.py | weniger

None
Sérgio
quelle
2
Diese Prüfung funktioniert in Python 2.7.13 nicht. sys.stdout.encodingwird automatisch basierend auf dem LC_CTYPEGebietsschemawert festgelegt.
Amphetamachine
1
mail.python.org/pipermail/python-list/2011-June/605938.html Das Beispiel dort funktioniert immer noch, dh wenn Sie ./a.py> out.txt verwenden, ist sys.stdout.encoding None
Sérgio
Ich hatte ein ähnliches Problem mit einem Synchronisierungsskript von Backblaze B2 und exportierte PYTHONIOENCODING = utf-8 löste mein Problem. Python 2.7 auf Debian Stretch.
0x3333
5

Ich hatte letzte Woche ein ähnliches Problem . Es war einfach in meiner IDE (PyCharm) zu beheben.

Hier war mein Fix:

Ausgehend von der PyCharm-Menüleiste: Datei -> Einstellungen ... -> Editor -> Dateicodierungen, dann festlegen: "IDE-Codierung", "Projektcodierung" und "Standardcodierung für Eigenschaftendateien" ALL bis UTF-8 und sie arbeitet jetzt wie ein Zauber.

Hoffe das hilft!

CLaFarge
quelle
4

Eine wohl bereinigte Version von Craig McQueens Antwort.

import sys, codecs
class EncodedOut:
    def __init__(self, enc):
        self.enc = enc
        self.stdout = sys.stdout
    def __enter__(self):
        if sys.stdout.encoding is None:
            w = codecs.getwriter(self.enc)
            sys.stdout = w(sys.stdout)
    def __exit__(self, exc_ty, exc_val, tb):
        sys.stdout = self.stdout

Verwendungszweck:

with EncodedOut('utf-8'):
    print u'ÅÄÖåäö'
Tompa
quelle
2

Ich könnte es mit einem Aufruf "automatisieren":

def __fix_io_encoding(last_resort_default='UTF-8'):
  import sys
  if [x for x in (sys.stdin,sys.stdout,sys.stderr) if x.encoding is None] :
      import os
      defEnc = None
      if defEnc is None :
        try:
          import locale
          defEnc = locale.getpreferredencoding()
        except: pass
      if defEnc is None :
        try: defEnc = sys.getfilesystemencoding()
        except: pass
      if defEnc is None :
        try: defEnc = sys.stdin.encoding
        except: pass
      if defEnc is None :
        defEnc = last_resort_default
      os.environ['PYTHONIOENCODING'] = os.environ.get("PYTHONIOENCODING",defEnc)
      os.execvpe(sys.argv[0],sys.argv,os.environ)
__fix_io_encoding() ; del __fix_io_encoding

Ja, es ist möglich, hier eine Endlosschleife zu erhalten, wenn dieses "Setenv" fehlschlägt.

jno
quelle
1
interessant, aber eine Pfeife scheint nicht glücklich darüber zu sein
n611x007
2

Ich dachte nur, ich würde hier etwas erwähnen, mit dem ich lange experimentieren musste, bevor ich endlich realisierte, was los war. Dies mag für alle hier so offensichtlich sein, dass sie sich nicht die Mühe gemacht haben, es zu erwähnen. Aber es hätte mir geholfen, wenn sie es getan hätten, also nach diesem Prinzip ...!

NB: Ich verwende Jython speziell, Version 2.7, daher gilt dies möglicherweise nicht für CPython ...

NB2: Die ersten beiden Zeilen meiner .py-Datei hier sind:

# -*- coding: utf-8 -*-
from __future__ import print_function

Der String-Konstruktionsmechanismus "%" (AKA "Interpolationsoperator") verursacht auch ZUSÄTZLICHE Probleme ... Wenn die Standardcodierung der "Umgebung" ASCII ist und Sie versuchen, so etwas zu tun

print( "bonjour, %s" % "fréd" )  # Call this "print A"

Sie werden keine Schwierigkeiten haben, in Eclipse zu laufen ... In einer Windows-CLI (DOS-Fenster) werden Sie feststellen, dass die Codierung Codepage 850 (mein Windows 7-Betriebssystem) oder etwas Ähnliches ist, das zumindest Zeichen mit europäischem Akzent verarbeiten kann werde arbeiten.

print( u"bonjour, %s" % "fréd" ) # Call this "print B"

wird auch funktionieren.

Wenn Sie, OTOH, von der CLI aus auf eine Datei verweisen, lautet die Standardcodierung None. Dies ist standardmäßig ASCII (auf meinem Betriebssystem ohnehin), das keinen der oben genannten Ausdrucke verarbeiten kann ... (gefürchtete Codierung) Error).

Dann könnten Sie daran denken, Ihr Standard mithilfe von umzuleiten

sys.stdout = codecs.getwriter('utf8')(sys.stdout)

und versuchen Sie, die CLI-Piping zu einer Datei auszuführen ... Sehr seltsamerweise funktioniert Druck A oben ... Aber Druck B oben löst den Codierungsfehler aus! Folgendes funktioniert jedoch einwandfrei:

print( u"bonjour, " + "fréd" ) # Call this "print C"

Die Schlussfolgerung, zu der ich (vorläufig) gekommen bin, ist, dass, wenn eine Zeichenfolge, die als Unicode- Zeichenfolge mit dem Präfix "u" angegeben ist, an den% -Handhabungsmechanismus gesendet wird, anscheinend die Verwendung der Standardumgebungscodierung unabhängig davon verwendet wird ob Sie stdout auf Weiterleiten eingestellt haben!

Wie die Leute damit umgehen, ist eine Frage der Wahl. Ich würde einen Unicode-Experten begrüßen, der sagt, warum dies passiert, ob ich es irgendwie falsch verstanden habe, was die bevorzugte Lösung dafür ist, ob es auch für CPython gilt , ob es in Python 3 passiert usw. usw.

Mike Nagetier
quelle
Das ist nicht seltsam, weil "fréd"es sich um eine Byte-Sequenz und nicht um eine Unicode-Zeichenfolge handelt, sodass der codecs.getwriterWrapper sie in Ruhe lässt. Sie brauchen eine Führung u, oder from __future__ import unicode_literals.
Matthias Urlichs
@MatthiasUrlichs OK ... danke ... Aber ich finde die Codierung nur einen der ärgerlichsten Aspekte der IT. Woher bekommen Sie Ihr Verständnis? Zum Beispiel habe ich gerade eine weitere Frage zur Codierung hier gestellt: stackoverflow.com/questions/44483067/… : Hier geht es um Java, Eclipse, Cygwin & Gradle. Wenn Ihr Fachwissen so weit geht, helfen Sie bitte ... vor allem möchte ich wissen, wo Sie mehr erfahren können!
Mike Nagetier
1

Ich bin in einer älteren Anwendung auf dieses Problem gestoßen, und es war schwierig zu identifizieren, wo was gedruckt wurde. Ich habe mir bei diesem Hack geholfen:

# encoding_utf8.py
import codecs
import builtins


def print_utf8(text, **kwargs):
    print(str(text).encode('utf-8'), **kwargs)


def print_utf8(fn):
    def print_fn(*args, **kwargs):
        return fn(str(*args).encode('utf-8'), **kwargs)
    return print_fn


builtins.print = print_utf8(print)

Zusätzlich zu meinem Skript test.py:

import encoding_utf8
string = 'Axwell Λ Ingrosso'
print(string)

Beachten Sie, dass dadurch ALLE Aufrufe zum Drucken geändert werden, um eine Codierung zu verwenden, sodass Ihre Konsole Folgendes druckt:

$ python test.py
b'Axwell \xce\x9b Ingrosso'
cessor
quelle
1

Unter Windows hatte ich dieses Problem sehr oft, wenn ich einen Python-Code in einem Editor (wie Sublime Text) ausführte, aber nicht, wenn ich ihn über die Befehlszeile ausführte.

Überprüfen Sie in diesem Fall die Parameter Ihres Editors. Im Fall von SublimeText wurde dies Python.sublime-buildgelöst:

{
  "cmd": ["python", "-u", "$file"],
  "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
  "selector": "source.python",
  "encoding": "utf8",
  "env": {"PYTHONIOENCODING": "utf-8", "LANG": "en_US.UTF-8"}
}
Basj
quelle