Impostazione della codifica corretta durante il piping di stdout in Python


343

Durante il piping dell'output di un programma Python, l'interprete Python viene confuso riguardo alla codifica e lo imposta su Nessuno. Ciò significa che un programma come questo:

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

funzionerà bene quando eseguito normalmente, ma non riesce con:

UnicodeEncodeError: il codec 'ascii' non può codificare il carattere u '\ xa0' in posizione 0: ordinale non compreso nell'intervallo (128)

se utilizzato in una sequenza di tubi.

Qual è il modo migliore per farlo funzionare durante il piping? Posso solo dirgli di usare qualunque codifica shell / filesystem / qualunque cosa stia usando?

I suggerimenti che ho visto finora sono di modificare direttamente il tuo site.py o di codificare il codice predefinito usando questo trucco:

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

Esiste un modo migliore per far funzionare le tubazioni?



2
Se hai questo problema su Windows, puoi anche eseguirlo chcp 65001prima di eseguire lo script. Questo può avere problemi, ma spesso aiuta e non richiede molta digitazione (meno di set PYTHONIOENCODING=utf_8).
Tomasz Gandor,

Il comando chcp non è lo stesso dell'impostazione di PYTHONIOENCODING. Penso che chcp sia solo una configurazione per il terminale stesso e non abbia nulla a che fare con la scrittura su un file (che è quello che stai facendo quando esegui il piping di stdout). Cerca setx PYTHONENCODING utf-8di renderlo permanente se vuoi salvare la digitazione.
ejm


Ho affrontato un problema in qualche modo correlato, e trovato una soluzione qui -> stackoverflow.com/questions/48782529/...
bkrishna2006

Risposte:


162

Il codice funziona quando viene eseguito in uno script perché Python codifica l'output in qualunque codifica venga utilizzata dall'applicazione terminale. Se stai eseguendo il piping devi codificarlo tu stesso.

Una regola empirica è: utilizzare sempre Unicode internamente. Decodifica ciò che ricevi e codifica ciò che invii.

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

Un altro esempio didattico è un programma Python per convertire tra ISO-8859-1 e UTF-8, rendendo tutto in maiuscolo.

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)

L'impostazione della codifica predefinita del sistema è una cattiva idea, perché alcuni moduli e librerie che usi possono fare affidamento sul fatto che è ASCII. Non farlo


11
Il problema è che l'utente non vuole specificare la codifica in modo esplicito. Vuole semplicemente usare Unicode per IO. E la codifica che usa dovrebbe essere una codifica specificata nelle impostazioni locali, non nelle impostazioni dell'applicazione terminale. AFAIK, Python 3 utilizza una codifica locale in questo caso. Il cambiamento sys.stdoutsembra un modo più piacevole.
Andrey Vlasovskikh,

4
La codifica / decodifica di ogni stringa in modo esplicito è destinata a causare bug quando manca una chiamata di codifica o decodifica o aggiunta una volta a molto da qualche parte. La codifica dell'uscita può essere impostata quando l'uscita è un terminale, quindi può essere impostata quando l'uscita non è un terminale. Esiste persino un ambiente LC_CTYPE standard per specificarlo. È un ma in pitone che non rispetta questo.
Rasmus Kaj,

65
Questa risposta è sbagliata Si dovrebbe non essere convertendo manualmente su ogni ingresso e uscita del programma; è fragile e completamente non mantenibile.
Glenn Maynard,

29
@Glenn Maynard: quindi qual è IYO la risposta giusta? È più utile dirci che dire semplicemente "Questa risposta è sbagliata"
smci,

14
@smci: la risposta è non modificare il tuo script, imposta PYTHONIOENCODINGse stai reindirizzando lo stdout dello script in Python 2.
jfs

168

Innanzitutto, per quanto riguarda questa soluzione:

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

Non è pratico stampare esplicitamente con una determinata codifica ogni volta. Sarebbe ripetitivo e soggetto a errori.

Una soluzione migliore è cambiare sys.stdoutall'inizio del programma, codificare con una codifica selezionata. Ecco una soluzione che ho trovato su Python: come viene scelto sys.stdout.encoding? , in particolare un commento di "toka":

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

7
sfortunatamente, la modifica di sys.stdout per accettare solo unicode interrompe molte librerie che si aspettano che accetti i bytestring codificati.
nosklo,

6
nosklo: Allora come può funzionare in modo affidabile e automatico quando l'output è un terminale?
Rasmus Kaj,

3
@Rasmus Kaj: basta definire la propria funzione di stampa Unicode e utilizzarla ogni volta che si desidera stampare Unicode: def myprint(unicodeobj): print unicodeobj.encode('utf-8')- si rileva automaticamente la codifica del terminale controllando sys.stdout.encoding, ma si dovrebbe considerare il caso in cui si trova None(cioè quando si reindirizza l'output su un file) quindi hai comunque bisogno di una funzione separata.
nosklo,

3
@nosklo: questo non consente a sys.stdout di accettare solo Unicode. È possibile passare sia str che unicode a StreamWriter.
Glenn Maynard,

9
Presumo che questa risposta fosse intesa per python2. Fai attenzione a questo sul codice che è destinato a supportare sia python2 che python3 . Per me si sta rompendo roba quando eseguito sotto python3.
mercoledì

130

Puoi provare a cambiare la variabile di ambiente "PYTHONIOENCODING" in "utf_8". Ho scritto una pagina sul mio calvario con questo problema .

Tl; dr del post del blog:

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

ti dà

utf_8
False
ANSI_X3.4-1968
ascii
utf_8
ö ☺ ☻

2
Cambiare sys.stdout.encoding forse non funziona, ma cambiando sys.stdout funziona: sys.stdout = codecs.getwriter(encoding)(sys.stdout). Questo può essere fatto all'interno del programma python, quindi l'utente non è obbligato a impostare una variabile env.
blueFast

7
@ jeckyll2hide: PYTHONIOENCODINGfunziona. Il modo in cui i byte vengono interpretati come testo viene definito dall'ambiente dell'utente . Lo script non dovrebbe assumere e dettare l'ambiente utente quale codifica dei caratteri utilizzare. Se Python non rileva automaticamente le impostazioni, PYTHONIOENCODINGpuò essere impostato per lo script. Non dovresti averne bisogno a meno che l'output non venga reindirizzato a un file / pipe.
jfs

8
+1. Onestamente penso che sia un bug di Python. Quando reindirizzo l'output voglio quegli stessi byte che sarebbero sul terminale, ma in un file. Forse non è per tutti ma è un buon default. Arrestare duramente senza alcuna spiegazione su un'operazione banale che di solito "funziona" è un difetto di default.
SnakE,

@SnakE: l'unico modo in cui posso razionalizzare il motivo per cui l'implementazione di Python imponesse intenzionalmente una scelta coraggiosa e permanente di codifica su stdout al momento dell'avvio, potrebbe essere al fine di impedire che eventuali contenuti codificati male vengano pubblicati in seguito. O modificarlo è solo una funzionalità non implementata, nel qual caso consentire all'utente di modificarlo in seguito sarebbe una ragionevole richiesta di funzionalità Python.
daveagp,

2
@daveagp Il mio punto è che il comportamento del mio programma non dovrebbe dipendere dal reindirizzamento o meno --- a meno che non lo voglia davvero, nel qual caso lo implemento da solo. Python si comporta in modo contrario alla mia esperienza con qualsiasi altro strumento console. Ciò viola il principio della minima sorpresa. Lo considero un difetto di progettazione a meno che non ci sia una motivazione molto forte.
SnakE

62
export PYTHONIOENCODING=utf-8

fare il lavoro, ma non è possibile impostarlo su Python stesso ...

ciò che possiamo fare è verificare se non è impostato e dire all'utente di impostarlo prima di chiamare lo script con:

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)

Aggiorna per rispondere al commento: il problema esiste solo quando si esegue il piping su stdout. Ho testato in Fedora 25 Python 2.7.13

python --version
Python 2.7.13

gatto b.py

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

print sys.stdout.encoding

in esecuzione ./b.py

UTF-8

in esecuzione ./b.py | Di meno

None

2
Tale controllo non funziona in Python 2.7.13. sys.stdout.encodingviene impostato automaticamente in base al LC_CTYPEvalore della locale.
anfetamachina,

1
mail.python.org/pipermail/python-list/2011-June/605938.html l'esempio funziona ancora, cioè quando usi ./a.py> out.txt sys.stdout.encoding è None
Sérgio

Ho avuto un problema simile con uno script di sincronizzazione di Backblaze B2 ed esportazione PYTHONIOENCODING = utf-8 ha risolto il mio problema. Python 2.7 su Debian Stretch.
0x3333

5

Ho avuto un problema simile la settimana scorsa . È stato facile da risolvere nel mio IDE (PyCharm).

Ecco la mia soluzione:

A partire dalla barra dei menu di PyCharm: File -> Impostazioni ... -> Editor -> Codifiche file, quindi impostare: "Codifica IDE", "Codifica progetto" e "Codifica predefinita per i file delle proprietà" TUTTO su UTF-8 e ora funziona a meraviglia.

Spero che questo ti aiuti!


4

Una discutibile versione disinfettata della risposta di Craig McQueen.

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

Uso:

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

2

Potrei "automatizzarlo" con una chiamata a:

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

Sì, è possibile ottenere un ciclo infinito qui se questo "setenv" fallisce.


1
interessante, ma una pipa non sembra essere contenta di questo
n611x007

2

Ho solo pensato di menzionare qualcosa qui che ho dovuto dedicare molto tempo a sperimentare prima di realizzare finalmente quello che stava succedendo. Questo può essere così ovvio per tutti qui che non si sono preoccupati di menzionarlo. Ma mi avrebbe aiutato se lo avessero fatto, quindi su quel principio ...!

NB: Sto usando Jython specificamente, v 2.7, quindi forse questo non può applicarsi a CPython ...

NB2: le prime due righe del mio file .py qui sono:

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

Il meccanismo di costruzione della stringa "%" (AKA "interpolation operator") causa anche problemi AGGIUNTIVI ... Se la codifica predefinita dell '"ambiente" è ASCII e si tenta di fare qualcosa di simile

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

Non avrai difficoltà a eseguire Eclipse ... In una CLI di Windows (finestra DOS) scoprirai che la codifica è la code page 850 (il mio sistema operativo Windows 7) o qualcosa di simile, che può gestire almeno i caratteri accentati europei, quindi lavorerò.

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

funzionerà anche.

Se, OTOH, sei diretto a un file dalla CLI, la codifica stdout sarà None, che per impostazione predefinita sarà ASCII (sul mio sistema operativo comunque), che non sarà in grado di gestire nessuna delle stampe sopra ... (codifica temuta errore).

Quindi potresti pensare di reindirizzare il tuo stdout usando

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

e prova a eseguire nella tubazione CLI su un file ... Stranamente, la stampa A sopra funzionerà ... Ma la stampa B sopra genererà l'errore di codifica! Tuttavia funzionerà correttamente:

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

La conclusione a cui sono giunto (provvisoriamente) è che se una stringa specificata come stringa Unicode che utilizza il prefisso "u" viene inviata al meccanismo di gestione%, sembra implicare l'uso della codifica dell'ambiente predefinita, indipendentemente da se hai impostato stdout per reindirizzare!

Come le persone affrontano questo è una questione di scelta. Darei il benvenuto a un esperto Unicode per dire perché questo accade, se ho sbagliato in qualche modo, quale sia la soluzione preferita a questo, se si applica anche a CPython , se succede in Python 3, ecc. Ecc.


Non è strano, è perché "fréd"è una sequenza di byte e non una stringa Unicode, quindi il codecs.getwriterwrapper la lascerà sola. Hai bisogno di un leader u, o from __future__ import unicode_literals.
Matthias Urlichs,

@MatthiasUrlichs OK ... grazie ... Ma trovo solo la codifica di uno degli aspetti più esasperanti dell'IT. Da dove prendi la tua comprensione? Ad esempio, ho appena pubblicato un'altra domanda sulla codifica qui: stackoverflow.com/questions/44483067/… : si tratta di Java, Eclipse, Cygwin & Gradle. Se la tua esperienza arriva fino a questo punto, ti preghiamo di aiutare ... soprattutto vorrei sapere dove saperne di più!
mike rodent,

1

Ho riscontrato questo problema in un'applicazione legacy ed era difficile identificare dove fosse stampato. Mi sono aiutato con questo trucco:

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

Oltre alla mia sceneggiatura, test.py:

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

Nota che questo cambia TUTTE le chiamate per stampare per usare una codifica, quindi la tua console stamperà questo:

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

1

Su Windows, ho avuto questo problema molto spesso quando eseguivo un codice Python da un editor (come Sublime Text), ma non se lo eseguivo dalla riga di comando.

In questo caso, controlla i parametri del tuo editor. Nel caso di SublimeText, questo ha Python.sublime-buildrisolto:

{
  "cmd": ["python", "-u", "$file"],
  "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
  "selector": "source.python",
  "encoding": "utf8",
  "env": {"PYTHONIOENCODING": "utf-8", "LANG": "en_US.UTF-8"}
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.