UnicodeDecodeError durante il reindirizzamento al file


100

Eseguo questo frammento due volte, nel terminale Ubuntu (codifica impostata su utf-8), una volta con ./test.pye poi con ./test.py >out.txt:

uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni

Senza reindirizzamento stampa spazzatura. Con il reindirizzamento ottengo un UnicodeDecodeError. Qualcuno può spiegare perché ricevo l'errore solo nel secondo caso, o ancora meglio dare una spiegazione dettagliata di cosa sta succedendo dietro le quinte in entrambi i casi?


Anche questa risposta potrebbe essere d'aiuto.
tzot

Quando provo a replicare la tua ricerca, ottengo un UnicodeEncodeError, non un UnicodeDecodeError. gist.github.com/jaraco/12abfc05872c65a4f3f6cd58b6f9be4d
Jason R. Coombs

Risposte:


252

L'intera chiave di questi problemi di codifica è capire che in linea di principio ci sono due concetti distinti di "stringa" : (1) stringa di caratteri e (2) stringa / array di byte. Questa distinzione è stata per lo più ignorata per molto tempo a causa dell'ubiquità storica delle codifiche con non più di 256 caratteri (ASCII, Latin-1, Windows-1252, Mac OS Roman, ...): queste codifiche mappano un insieme di caratteri comuni a numeri compresi tra 0 e 255 (cioè byte); lo scambio relativamente limitato di file prima dell'avvento del web rendeva tollerabile questa situazione di codifiche incompatibili, poiché la maggior parte dei programmi poteva ignorare il fatto che c'erano più codifiche fintanto che producevano testo che rimaneva sullo stesso sistema operativo: tali programmi semplicemente tratta il testo come byte (tramite la codifica utilizzata dal sistema operativo). La visualizzazione corretta e moderna separa correttamente questi due concetti di stringa, in base ai seguenti due punti:

  1. I caratteri sono per lo più estranei ai computer : è possibile disegnarli su una lavagna, ecc., Come ad esempio بايثون, 中 蟒 e 🐍. I "caratteri" per le macchine includono anche "istruzioni di disegno" come ad esempio spazi, ritorno a capo, istruzioni per impostare la direzione di scrittura (per l'arabo, ecc.), Accenti, ecc. Un elenco di caratteri molto grande è incluso nello standard Unicode ; copre la maggior parte dei personaggi conosciuti.

  2. D'altra parte, i computer hanno bisogno di rappresentare caratteri astratti in qualche modo: per questo, usano array di byte (numeri compresi tra 0 e 255 inclusi), perché la loro memoria è composta da blocchi di byte. Il processo necessario che converte i caratteri in byte è chiamato codifica . Pertanto, un computer richiede una codifica per rappresentare i caratteri. Qualsiasi testo presente sul tuo computer viene codificato (fino a quando non viene visualizzato), sia che venga inviato a un terminale (che si aspetta caratteri codificati in un modo specifico), sia che venga salvato in un file. Per essere visualizzati o "compresi" correttamente (ad esempio, dall'interprete Python), i flussi di byte vengono decodificati in caratteri. Alcune codifiche(UTF-8, UTF-16, ...) sono definiti da Unicode per il suo elenco di caratteri (Unicode quindi definisce sia un elenco di caratteri sia codifiche per questi caratteri - ci sono ancora punti in cui si vede l'espressione "Codifica Unicode" come un modo per fare riferimento all'onnipresente UTF-8, ma questa è una terminologia errata, poiché Unicode fornisce più codifiche).

In sintesi, i computer devono rappresentare internamente i caratteri con byte e lo fanno attraverso due operazioni:

Codifica : caratteri → byte

Decodifica : byte → caratteri

Alcune codifiche non possono codificare tutti i caratteri (es. ASCII), mentre (alcune) codifiche Unicode consentono di codificare tutti i caratteri Unicode. Anche la codifica non è necessariamente univoca , perché alcuni caratteri possono essere rappresentati direttamente o come combinazione (ad esempio di un carattere di base e di accenti).

Si noti che il concetto di nuova riga aggiunge uno strato di complicazione , poiché può essere rappresentato da diversi caratteri (di controllo) che dipendono dal sistema operativo (questo è il motivo per la modalità di lettura dei file di nuova riga universale di Python ).

Quello che ho chiamato "carattere" sopra è quello che Unicode chiama un " carattere percepito dall'utente ". Un singolo carattere percepito dall'utente a volte può essere rappresentato in Unicode combinando parti di caratteri (carattere di base, accenti, ...) trovati in diversi indici nell'elenco Unicode, che sono chiamati " punti di codice " - questi punti di codice possono essere combinati insieme per formare un "grapheme cluster". Unicode porta così a un terzo concetto di stringa, costituito da una sequenza di punti di codice Unicode, che si trova tra byte e stringhe di caratteri, e che è più vicino a quest'ultima. Li chiamerò " stringhe Unicode " (come in Python 2).

Mentre Python può stampare stringhe di caratteri (percepiti dall'utente), le stringhe non byte Python sono essenzialmente sequenze di punti di codice Unicode , non di caratteri percepiti dall'utente. I valori del punto di codice sono quelli usati nella sintassi delle stringhe di Python \ue \UUnicode. Non devono essere confusi con la codifica di un carattere (e non devono avere alcuna relazione con esso: i punti di codice Unicode possono essere codificati in vari modi).

Ciò ha una conseguenza importante: la lunghezza di una stringa Python (Unicode) è il suo numero di punti di codice, che non è sempre il suo numero di caratteri percepiti dall'utente : quindi s = "\u1100\u1161\u11a8"; print(s, "len", len(s))(Python 3) dà 각 len 3nonostante sabbia un unico utente percepito (coreano) carattere (perché è rappresentato con 3 punti di codice, anche se non è necessario, come print("\uac01")mostra). Tuttavia, in molte circostanze pratiche, la lunghezza di una stringa è il suo numero di caratteri percepiti dall'utente, perché molti caratteri sono tipicamente memorizzati da Python come un singolo punto di codice Unicode.

In Python 2 , le stringhe Unicode sono chiamate… "stringhe Unicode" ( unicodetipo, forma letterale u"…"), mentre gli array di byte sono "stringhe" ( strtipo, dove l'array di byte può essere costruito per esempio con stringhe letterali "…"). In Python 3 , le stringhe Unicode sono semplicemente chiamate "stringhe" ( strtipo, forma letterale "…"), mentre gli array di byte sono "byte" ( bytestipo, forma letterale b"…"). Di conseguenza, qualcosa di simile "🐍"[0]dà un risultato diverso in Python 2 ( '\xf0', un byte) e Python 3 ( "🐍", il primo e unico carattere).

Con questi pochi punti chiave, dovresti essere in grado di comprendere la maggior parte delle domande relative alla codifica!


Normalmente, quando stampi u"…" su un terminale , non dovresti ottenere spazzatura: Python conosce la codifica del tuo terminale. In effetti, puoi verificare cosa si aspetta dalla codifica il terminale:

% python
Python 2.7.6 (default, Nov 15 2013, 15:20:37) 
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.2.79)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print sys.stdout.encoding
UTF-8

Se i tuoi caratteri di input possono essere codificati con la codifica del terminale, Python lo farà e invierà i byte corrispondenti al tuo terminale senza lamentarsi. Il terminale farà quindi del suo meglio per visualizzare i caratteri dopo aver decodificato i byte di input (nel peggiore dei casi il carattere del terminale non ha alcuni caratteri e stamperà invece una sorta di vuoto).

Se i caratteri immessi non possono essere codificati con la codifica del terminale, significa che il terminale non è configurato per visualizzare questi caratteri. Python si lamenterà (in Python con un UnicodeEncodeErrorpoiché la stringa di caratteri non può essere codificata in un modo che si adatta al tuo terminale). L'unica soluzione possibile è utilizzare un terminale in grado di visualizzare i caratteri (sia configurando il terminale in modo che accetti una codifica che possa rappresentare i vostri caratteri, sia utilizzando un programma terminale diverso). Questo è importante quando si distribuiscono programmi che possono essere utilizzati in ambienti diversi: i messaggi che si stampano dovrebbero essere rappresentabili nel terminale dell'utente. A volte è quindi meglio attenersi a stringhe che contengono solo caratteri ASCII.

Tuttavia, quando si reindirizza o si reindirizza l'output del programma, in genere non è possibile sapere quale sia la codifica di input del programma ricevente e il codice sopra restituisce una codifica predefinita: Nessuno (Python 2.7) o UTF-8 ( Python 3):

% python2.7 -c "import sys; print sys.stdout.encoding" | cat
None
% python3.4 -c "import sys; print(sys.stdout.encoding)" | cat
UTF-8

La codifica di stdin, stdout e stderr può comunque essere impostata tramite la PYTHONIOENCODINGvariabile d'ambiente, se necessario:

% PYTHONIOENCODING=UTF-8 python2.7 -c "import sys; print sys.stdout.encoding" | cat
UTF-8

Se la stampa su un terminale non produce ciò che ti aspetti, puoi controllare che la codifica UTF-8 inserita manualmente sia corretta; per esempio, il tuo primo carattere ( \u001A) non è stampabile, se non sbaglio .

Su http://wiki.python.org/moin/PrintFails , puoi trovare una soluzione come la seguente, per Python 2.x:

import codecs
import locale
import sys

# Wrap sys.stdout into a StreamWriter to allow writing unicode.
sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout) 

uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni

Per Python 3, puoi controllare una delle domande poste in precedenza su StackOverflow.


2
@singularity: grazie! Ho aggiunto alcune informazioni per Python 3.
Eric O Lebigot

2
Grazie amico! Avevo bisogno di questa spiegazione per così tanto tempo ... È un peccato che posso darti solo un voto positivo.
mik01aj

3
Sono contento di essere stato di aiuto, @ m01! Una delle motivazioni per scrivere questa risposta era che c'erano molte pagine sul web su Unicode e Python, ma ho scoperto che nonostante fossero interessanti, non mi hanno mai completamente permesso di risolvere problemi concreti di codifica ... Credo davvero che tenendo presente il i principi che si trovano in questa risposta e dedicare del tempo a utilizzarli quando si risolvono problemi concreti di codifica aiuta molto.
Eric O Lebigot,

3
Questa è senza dubbio la migliore spiegazione di unicode e python di sempre. Il Python Unicode HOWTO dovrebbe essere sostituito con questo.
stantonk

1
Qui, lasciami disegnare il carattere "sostituzione da destra a sinistra" su questa lavagna ...
icktoofay

20

Python codifica sempre le stringhe Unicode quando si scrive su un terminale, file, pipe, ecc. Quando si scrive su un terminale Python può generalmente determinare la codifica del terminale e usarla correttamente. Quando si scrive su un file o su una pipe, Python utilizza per impostazione predefinita la codifica "ascii" a meno che non venga esplicitamente indicato diversamente. Si può dire a Python cosa fare quando convoglia l'output attraverso la PYTHONIOENCODINGvariabile d'ambiente. Una shell può impostare questa variabile prima di reindirizzare l'output di Python a un file o pipe in modo che sia nota la codifica corretta.

Nel tuo caso hai stampato 4 caratteri non comuni che il tuo terminale non supportava nel suo carattere. Ecco alcuni esempi per spiegare il comportamento, con caratteri che sono effettivamente supportati dal mio terminale (che utilizza cp437, non UTF-8).

Esempio 1

Notare che il #codingcommento indica la codifica in cui è salvato il file sorgente . Ho scelto utf8 in modo da poter supportare i caratteri nella sorgente che il mio terminale non poteva. Codifica reindirizzata a stderr in modo che possa essere vista quando reindirizzata a un file.

#coding: utf8
import sys
uni = u'αßΓπΣσµτΦΘΩδ∞φ'
print >>sys.stderr,sys.stdout.encoding
print uni

Output (eseguito direttamente dal terminale)

cp437
αßΓπΣσµτΦΘΩδ∞φ

Python ha determinato correttamente la codifica del terminale.

Output (reindirizzato a file)

None
Traceback (most recent call last):
  File "C:\ex.py", line 5, in <module>
    print uni
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-13: ordinal not in range(128)

Python non è stato in grado di determinare la codifica (Nessuno), quindi ha utilizzato il valore predefinito "ascii". ASCII supporta solo la conversione dei primi 128 caratteri di Unicode.

Output (reindirizzato al file, PYTHONIOENCODING = cp437)

cp437

e il mio file di output era corretto:

C:\>type out.txt
αßΓπΣσµτΦΘΩδ∞φ

Esempio 2

Ora inserirò un carattere nella sorgente che non è supportato dal mio terminale:

#coding: utf8
import sys
uni = u'αßΓπΣσµτΦΘΩδ∞φ马' # added Chinese character at end.
print >>sys.stderr,sys.stdout.encoding
print uni

Output (eseguito direttamente dal terminale)

cp437
Traceback (most recent call last):
  File "C:\ex.py", line 5, in <module>
    print uni
  File "C:\Python26\lib\encodings\cp437.py", line 12, in encode
    return codecs.charmap_encode(input,errors,encoding_map)
UnicodeEncodeError: 'charmap' codec can't encode character u'\u9a6c' in position 14: character maps to <undefined>

Il mio terminale non ha capito l'ultimo carattere cinese.

Output (eseguito direttamente, PYTHONIOENCODING = 437: sostituisci)

cp437
αßΓπΣσµτΦΘΩδ∞φ?

I gestori di errori possono essere specificati con la codifica. In questo caso i caratteri sconosciuti sono stati sostituiti con ?. ignoree ci xmlcharrefreplacesono altre opzioni. Quando si utilizza UTF8 (che supporta la codifica di tutti i caratteri Unicode) non verranno mai effettuate sostituzioni, ma il carattere utilizzato per visualizzare i caratteri deve comunque supportarli.


Non è esattamente vero che "Quando si scrive su un file o pipe, Python utilizza per impostazione predefinita la codifica 'ascii' a meno che non venga esplicitamente detto diversamente.". Infatti, Python 3 utilizza UTF-8, su Mac OS X / Fink.
Eric O Lebigot

2
Sì, il valore predefinito di Python 3 è "utf8", ma in base all'esempio dell'OP, sta usando Python 2.X, il cui valore predefinito è "ascii".
Mark Tolonen

Non sono riuscito a ottenere l'output corretto manipolando PYTHONIOENCODING. Fare print string.encode("UTF-8")come suggerito da @Ismail ha funzionato per me.
tripleee

puoi vedere i caratteri cinesi se il tuo font li supporta anche se chcpcodepage non li supporta. Per evitare UnicodeEncodeError: 'charmap', puoi installare win-unicode-consolepackage.
jfs

Il mio problema è che la CLI di python-gitlab stampa bene i caratteri cinesi in cmd ma i caratteri sono spazzatura dopo essere stati reindirizzati nei file. PYTHONIOENCODING=utf-8risolve il problema.
ElpieKay

12

Codificalo durante la stampa

uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni.encode("utf-8")

Questo perché quando esegui lo script manualmente, Python lo codifica prima di inviarlo al terminale, quando lo installi Python non lo codifica da solo, quindi devi codificare manualmente quando fai I / O.


4
Ancora non risponde alla domanda che WTH sta succedendo qui. Perché, di punto in bianco, decide di codificare solo quando viene reindirizzato, quando questo dovrebbe essere completamente trasparente al processo.
Maxim Sloyko

Perché Python non lo codifica durante l'esecuzione del reindirizzamento? Python controlla e decide esplicitamente che farà le cose in modo diverso solo per essere difficile?
Arafangion

1
python ha anche un modo per distinguere le due situazioni? Ho pensato (fino ad ora ...) che non ci fosse modo di saperlo.
zedoo

4
Python può verificare se l'output è un terminale, se viene inviato a una pipe, il tipo di terminale sarà "stupido". Immagino che "stupido" dovrebbe dirti perché Python non cerca di fare nulla di automatico in questo caso, può fallire.
ismail

1
produce mojibake se l'ambiente utilizza una codifica dei caratteri incompatibile con utf-8 (ad esempio, è comune su Windows). Non codificare la codifica dei caratteri del tuo ambiente all'interno dello script. Configura le tue impostazioni locali, o PYTHONIOENCODING, o installa win-unicode-console(Windows), o accetta un parametro della riga di comando (se necessario).
jfs
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.