Grazie a frammenti di varie risposte, penso che possiamo ricucire una spiegazione.
Provando a stampare una stringa unicode, u '\ xe9', Python tenta implicitamente di codificare quella stringa usando lo schema di codifica attualmente memorizzato in sys.stdout.encoding. Python effettivamente prende questa impostazione dall'ambiente da cui è stata avviata. Se non riesce a trovare una codifica corretta dall'ambiente, solo allora ripristina l' impostazione predefinita , ASCII.
Ad esempio, utilizzo una shell bash che codifica per impostazione predefinita UTF-8. Se avvio Python da esso, raccoglie e usa quell'impostazione:
$ python
>>> import sys
>>> print sys.stdout.encoding
UTF-8
Lasciamo per un momento la shell di Python e impostiamo l'ambiente di bash con una codifica fasulla:
$ export LC_CTYPE=klingon
# we should get some error message here, just ignore it.
Quindi riavviare la shell Python e verificare che ripristini effettivamente la codifica ASCII predefinita.
$ python
>>> import sys
>>> print sys.stdout.encoding
ANSI_X3.4-1968
Bingo!
Se ora provi a generare un carattere unicode al di fuori di ASCII, dovresti ricevere un bel messaggio di errore
>>> print u'\xe9'
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9'
in position 0: ordinal not in range(128)
Consente di uscire da Python e scartare la shell bash.
Osserveremo ora cosa succede dopo che Python emette stringhe. Per questo inizieremo prima una shell bash all'interno di un terminale grafico (utilizzo Gnome Terminal) e imposteremo il terminale per decodificare l'output con ISO-8859-1 aka latin-1 (i terminali grafici di solito hanno un'opzione per impostare il carattere Codifica in uno dei loro menu a discesa). Si noti che ciò non modifica la codifica dell'ambiente di shell reale , ma cambia solo il modo in cui il terminale stesso decodificherà l'output fornito, un po 'come fa un browser web. È quindi possibile modificare la codifica del terminale, indipendentemente dall'ambiente della shell. Quindi avviamo Python dalla shell e verificiamo che sys.stdout.encoding sia impostato sulla codifica dell'ambiente di shell (UTF-8 per me):
$ python
>>> import sys
>>> print sys.stdout.encoding
UTF-8
>>> print '\xe9' # (1)
é
>>> print u'\xe9' # (2)
é
>>> print u'\xe9'.encode('latin-1') # (3)
é
>>>
(1) python emette una stringa binaria così com'è, il terminale la riceve e cerca di far corrispondere il suo valore con la mappa di caratteri latin-1. In latino-1, 0xe9 o 233 restituisce il carattere "é" e quindi questo è ciò che viene visualizzato dal terminale.
(2) Python tenta di codificare implicitamente la stringa Unicode con qualunque schema sia attualmente impostato in sys.stdout.encoding, in questo caso è "UTF-8". Dopo la codifica UTF-8, la stringa binaria risultante è '\ xc3 \ xa9' (vedere la spiegazione successiva). Il terminale riceve lo stream in quanto tale e tenta di decodificare 0xc3a9 usando latin-1, ma latin-1 va da 0 a 255 e quindi decodifica solo i flussi 1 byte alla volta. 0xc3a9 è lungo 2 byte, il decodificatore latino-1 quindi lo interpreta come 0xc3 (195) e 0xa9 (169) e produce 2 caratteri: Ã e ©.
(3) python codifica il punto di codice unicode u '\ xe9' (233) con lo schema latino-1. Risulta che l'intervallo di punti del codice latino-1 è 0-255 e punta allo stesso identico carattere di Unicode all'interno di quell'intervallo. Pertanto, i punti di codice Unicode in quell'intervallo produrranno lo stesso valore se codificati in latino-1. Quindi u '\ xe9' (233) codificato in latino-1 produce anche la stringa binaria '\ xe9'. Il terminale riceve quel valore e cerca di abbinarlo sulla mappa dei caratteri latino-1. Proprio come case (1), produce "é" ed è quello che viene visualizzato.
Ora cambiamo le impostazioni di codifica del terminale in UTF-8 dal menu a discesa (come se cambiassi le impostazioni di codifica del tuo browser web). Non è necessario arrestare Python o riavviare la shell. La codifica del terminale ora corrisponde a quella di Python. Proviamo di nuovo a stampare:
>>> print '\xe9' # (4)
>>> print u'\xe9' # (5)
é
>>> print u'\xe9'.encode('latin-1') # (6)
>>>
(4) python genera una stringa binaria così com'è. Il terminale tenta di decodificare quel flusso con UTF-8. Ma UTF-8 non comprende il valore 0xe9 (vedere la spiegazione successiva) e non è quindi in grado di convertirlo in un punto di codice unicode. Nessun punto di codice trovato, nessun carattere stampato.
(5) python tenta di codificare implicitamente la stringa Unicode con qualunque cosa sia in sys.stdout.encoding. Ancora "UTF-8". La stringa binaria risultante è '\ xc3 \ xa9'. Il terminale riceve lo stream e tenta di decodificare 0xc3a9 anche usando UTF-8. Restituisce il valore del codice 0xe9 (233), che sulla mappa dei caratteri Unicode punta al simbolo "é". Il terminale visualizza "é".
(6) python codifica la stringa unicode con latin-1, produce una stringa binaria con lo stesso valore '\ xe9'. Ancora una volta, per il terminale questo è praticamente lo stesso del caso (4).
Conclusioni: - Python genera stringhe non unicode come dati non elaborati, senza considerare la codifica predefinita. Il terminale semplicemente li visualizza se la sua codifica corrente corrisponde ai dati. - Python genera stringhe Unicode dopo averle codificate usando lo schema specificato in sys.stdout.encoding. - Python ottiene quell'impostazione dall'ambiente della shell. - il terminale visualizza l'output in base alle proprie impostazioni di codifica. - la codifica del terminale è indipendente da quella della shell.
Maggiori dettagli su unicode, UTF-8 e latin-1:
Unicode è fondamentalmente una tabella di caratteri in cui alcuni tasti (punti di codice) sono stati assegnati in modo convenzionale per indicare alcuni simboli. ad es. per convenzione è stato deciso che la chiave 0xe9 (233) è il valore che punta al simbolo 'é'. ASCII e Unicode usano gli stessi punti di codice da 0 a 127, così come latin-1 e Unicode da 0 a 255. Cioè, 0x41 punti a 'A' in ASCII, latin-1 e Unicode, 0xc8 punti a 'Ü' in latin-1 e Unicode, 0xe9 punta a 'é' in latin-1 e Unicode.
Quando si lavora con dispositivi elettronici, i punti di codice Unicode necessitano di un modo efficiente per essere rappresentati elettronicamente. Ecco a cosa servono gli schemi di codifica. Esistono vari schemi di codifica Unicode (utf7, UTF-8, UTF-16, UTF-32). L'approccio di codifica più intuitivo e diretto sarebbe semplicemente usare il valore di un punto di codice nella mappa Unicode come valore per la sua forma elettronica, ma attualmente Unicode ha oltre un milione di punti di codice, il che significa che alcuni di essi richiedono 3 byte per essere espresso. Per lavorare in modo efficiente con il testo, una mappatura da 1 a 1 sarebbe piuttosto impraticabile, poiché richiederebbe che tutti i punti di codice vengano archiviati esattamente nella stessa quantità di spazio, con un minimo di 3 byte per carattere, indipendentemente dalla loro effettiva necessità.
La maggior parte degli schemi di codifica ha carenze in termini di spazio, i più economici non coprono tutti i punti di codice unicode, ad esempio ascii copre solo i primi 128, mentre latino-1 copre i primi 256. Altri che cercano di essere più completi finiscono anche essendo dispendiosi, poiché richiedono più byte del necessario, anche per caratteri "economici" comuni. UTF-16, ad esempio, utilizza un minimo di 2 byte per carattere, inclusi quelli nell'intervallo ascii ('B' che è 65, richiede ancora 2 byte di memoria in UTF-16). UTF-32 è ancora più dispendioso in quanto memorizza tutti i caratteri in 4 byte.
UTF-8 sembra aver risolto abilmente il dilemma, con uno schema in grado di memorizzare punti di codice con una quantità variabile di spazi byte. Come parte della sua strategia di codifica, UTF-8 allaccia punti di codice con bit di flag che indicano (presumibilmente ai decodificatori) i loro requisiti di spazio e i loro confini.
Codifica UTF-8 dei punti di codice unicode nell'intervallo ascii (0-127):
0xxx xxxx (in binary)
- le x mostrano lo spazio effettivo riservato per "memorizzare" il punto di codice durante la codifica
- Lo 0 iniziale è un flag che indica al decodificatore UTF-8 che questo punto di codice richiederà solo 1 byte.
- al momento della codifica, UTF-8 non modifica il valore dei punti di codice in quell'intervallo specifico (ovvero 65 codificato in UTF-8 è anche 65). Considerando che anche Unicode e ASCII sono compatibili nello stesso intervallo, incidentalmente rende UTF-8 e ASCII compatibili anche in quell'intervallo.
es. il punto di codice Unicode per 'B' è '0x42' o 0100 0010 in binario (come abbiamo detto, è lo stesso in ASCII). Dopo la codifica in UTF-8 diventa:
0xxx xxxx <-- UTF-8 encoding for Unicode code points 0 to 127
*100 0010 <-- Unicode code point 0x42
0100 0010 <-- UTF-8 encoded (exactly the same)
Codifica UTF-8 dei punti di codice Unicode sopra 127 (non ascii):
110x xxxx 10xx xxxx <-- (from 128 to 2047)
1110 xxxx 10xx xxxx 10xx xxxx <-- (from 2048 to 65535)
- i bit iniziali "110" indicano al decodificatore UTF-8 l'inizio di un punto di codice codificato in 2 byte, mentre "1110" indica 3 byte, 11110 indica 4 byte e così via.
- i bit flag '10' interni sono usati per segnalare l'inizio di un byte interno.
- ancora una volta, le x indicano lo spazio in cui è memorizzato il valore del punto del codice Unicode dopo la codifica.
ad es. 'é' il punto di codice Unicode è 0xe9 (233).
1110 1001 <-- 0xe9
Quando UTF-8 codifica questo valore, determina che il valore è maggiore di 127 e inferiore a 2048, pertanto deve essere codificato in 2 byte:
110x xxxx 10xx xxxx <-- UTF-8 encoding for Unicode 128-2047
***0 0011 **10 1001 <-- 0xe9
1100 0011 1010 1001 <-- 'é' after UTF-8 encoding
C 3 A 9
Il codice Unicode 0xe9 punta dopo la codifica UTF-8 diventa 0xc3a9. È esattamente come lo riceve il terminale. Se il tuo terminale è impostato per decodificare le stringhe usando latin-1 (una delle codifiche legacy non unicode), vedrai à ©, perché accade che 0xc3 in latin-1 punti a à e 0xa9 a ©.