Perché NON dovremmo usare sys.setdefaultencoding ("utf-8") in uno script PY?


166

Ho visto alcuni script Py che usano questo nella parte superiore dello script. In quali casi si dovrebbe usarlo?

import sys
reload(sys)
sys.setdefaultencoding("utf-8")

2
c'è un problema con l'utilizzo di questo in ipython,% time smette di funzionare github.com/ipython/ipython/issues/8071
seanv507

3
@ seanv507, leggi le risposte - usarlo è seriamente scoraggiato
Alastair McCormack,


2
In che modo questo non è un duplicato esatto di Dangers of sys.setdefaultencoding ('utf-8') ? Anche se questo (2010) chiede prima di quello (2015)? Ma anche questa domanda ha buone risposte. Cosa fare? Inoltre, per essere chiari, questa domanda ha senso solo su Python 2 e non su 3, ma questo non è in nessun luogo taggato o menzionato.
smci,

vale la pena leggere prima di immergersi nelle risposte SO: pythonhosted.org/kitchen/unicode-frustrations.html
ccpizza

Risposte:


141

Come da documentazione: questo ti permette di passare dall'ASCII predefinito ad altre codifiche come UTF-8, che il runtime di Python utilizzerà ogni volta che dovrà decodificare un buffer di stringa in Unicode.

Questa funzione è disponibile solo al momento dell'avvio di Python, quando Python esegue la scansione dell'ambiente. Deve essere chiamato in un modulo a livello di sistema sitecustomize.py, dopo che questo modulo è stato valutato, la setdefaultencoding()funzione viene rimossa dal sysmodulo.

L'unico modo per usarlo effettivamente è con un hack di ricarica che riporta l'attributo.

Inoltre, l'uso di sys.setdefaultencoding()è sempre stato scoraggiato , ed è diventato una no-op in py3k. La codifica di py3k è cablata su "utf-8" e la sua modifica genera un errore.

Suggerisco alcuni suggerimenti per la lettura:


6
Roba fantastica, anche se c'è un po 'di morte per troppe informazioni qui. Ho imparato di più solo concentrandomi su questo articolo: blog.notdot.net/2010/07/Getting-unicode-right-in-Python
mbb

3
Vorrei aggiungere che la codifica predefinita viene utilizzata anche per la codifica (quando si scrive sys.stdoutquando ha una Nonecodifica, come quando si reindirizza l'output di un programma Python).
Eric O Lebigot,

14
+1 per "l'uso di sys.setdefaultencoding()è sempre stato scoraggiato"
jfs

7
'hard-wired to utf-8' non è vero, non è cablato e non è sempre UTF-8. LC_ALL=en_US.UTF-8 python3 -c 'import sys; print(sys.stdout.encoding)'UTF-8ma LC_ALL=C python3 -c 'import sys; print(sys.stdout.encoding)'ANSI_X3.4-1968(o forse qualcos'altro)
Tino,

7
@Tino, la codifica della console è separata dalla codifica predefinita.
Alastair McCormack,

59

tl; dr

La risposta è MAI ! (a meno che tu non sappia davvero cosa stai facendo)

9/10 volte la soluzione può essere risolta con una corretta comprensione della codifica / decodifica.

1/10 persone hanno una locale o un ambiente definiti in modo errato e devono impostare:

PYTHONIOENCODING="UTF-8"  

nel loro ambiente per risolvere i problemi di stampa della console.

Che cosa fa?

sys.setdefaultencoding("utf-8")(barrato per evitare il riutilizzo) modifica la codifica / decodifica predefinita utilizzata ogni volta che Python 2.x deve convertire un Unicode () in uno str () (e viceversa) e la codifica non viene fornita. Vale a dire:

str(u"\u20AC")
unicode("€")
"{}".format(u"\u20AC") 

In Python 2.x, la codifica predefinita è impostata su ASCII e gli esempi sopra non riusciranno con:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 0: ordinal not in range(128)

(La mia console è configurata come UTF-8, quindi "€" = '\xe2\x82\xac', quindi eccezione su \xe2)

o

UnicodeEncodeError: 'ascii' codec can't encode character u'\u20ac' in position 0: ordinal not in range(128)

sys.setdefaultencoding("utf-8")permetterà a questi di funzionare per me , ma non funzionerà necessariamente per le persone che non usano UTF-8. L'impostazione predefinita di ASCII garantisce che i presupposti della codifica non vengano inseriti nel codice

consolle

sys.setdefaultencoding("utf-8")ha anche un effetto collaterale di apparire da correggere sys.stdout.encoding, usato quando si stampano i caratteri sulla console. Python utilizza le impostazioni locali dell'utente (Linux / OS X / Un * x) o la tabella codici (Windows) per impostarle. Occasionalmente, la localizzazione di un utente viene interrotta e richiede solo PYTHONIOENCODINGdi correggere la codifica della console .

Esempio:

$ export LANG=en_GB.gibberish
$ python
>>> import sys
>>> sys.stdout.encoding
'ANSI_X3.4-1968'
>>> print u"\u20AC"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u20ac' in position 0: ordinal not in range(128)
>>> exit()

$ PYTHONIOENCODING=UTF-8 python
>>> import sys
>>> sys.stdout.encoding
'UTF-8'
>>> print u"\u20AC"
€

Cosa c'è di così male con sys.setdefaultencoding ("utf-8") ?

Le persone hanno sviluppato Python 2.x per 16 anni con la consapevolezza che la codifica predefinita è ASCII. UnicodeErrorsono stati scritti metodi di gestione delle eccezioni per gestire le conversioni da stringa a Unicode su stringhe che non contengono ASCII.

Da https://anonbadger.wordpress.com/2015/06/16/why-sys-setdefaultencoding-will-break-code/

def welcome_message(byte_string):
    try:
        return u"%s runs your business" % byte_string
    except UnicodeError:
        return u"%s runs your business" % unicode(byte_string,
            encoding=detect_encoding(byte_string))

print(welcome_message(u"Angstrom (Å®)".encode("latin-1"))

Prima di impostare la codifica predefinita, questo codice non sarebbe stato in grado di decodificare "Å" nella codifica ASCII e quindi inserire il gestore eccezioni per indovinare la codifica e trasformarla correttamente in Unicode. Stampa: Angstrom (Å®) gestisce la tua attività. Dopo aver impostato la codifica predefinita su utf-8, il codice troverà che byte_string può essere interpretato come utf-8 e quindi manipolerà i dati e lo restituirà invece: Angstrom (Ů) gestisce la tua attività.

Cambiare quella che dovrebbe essere una costante avrà effetti drammatici sui moduli da cui dipendi. È meglio semplicemente correggere i dati in entrata e in uscita dal codice.

Esempio di problema

Mentre l'impostazione della codifica predefinita su UTF-8 non è la causa principale nell'esempio seguente, mostra come vengono mascherati i problemi e come, quando cambia la codifica di input, il codice si interrompe in modo non visibile: UnicodeDecodeError: il codec 'utf8' può decodifica byte 0x80 in posizione 3131: byte iniziale non valido


2
Sebbene ci siano sorprese sys.setdefaultencoding("utf-8"), è bene far sì che il codice si comporti più come Python 3. È il 2017 ora. Anche quando hai scritto la risposta nel 2015, penso che fosse già meglio guardare avanti anziché indietro. In realtà è stata la soluzione più semplice per me, quando ho scoperto che il mio codice si comportava diversamente in Python 2 a seconda che l'output fosse reindirizzato (problema molto brutto per Python 2). Inutile dire che l'ho già fatto # coding: utf-8e non ho bisogno di soluzioni alternative per Python 3 (in realtà devo mascherare il setdefaultencodingcontrollo della versione usando).
Yongwei Wu,

È fantastico e funziona per te ma sys.setdefaultencoding("utf-8")non rende il tuo codice Py 2.x compatibile con Python 3. Né corregge i moduli esterni che presuppongono che la codifica predefinita sia ASCII. Rendere compatibile il tuo codice Python 3 è molto semplice e non richiede questo brutto hack. Ad esempio perché questo causa problemi molto reali, vedi la mia esperienza con Amazon che scherza con questo presupposto: stackoverflow.com/questions/39465220/…
Alastair McCormack,

1
@AlastairMcCormack rock, Il mio sito è stato da mesi e non riuscivo a capire cosa fare. Infine, ha PYTHONIOENCODING="UTF-8"aiutato il mio ambiente Python2.7 Django-1.11. Grazie.
sam,

So che hai copiato l'esempio, ma posso trovare il pacchetto detect_encoding.
dlamblin,

@dlamblin L'esempio di codice serve a provare la citazione e non dovrebbe essere usato nel tuo codice. Immagina che detect_encodingsia un metodo in grado di rilevare la codifica di una stringa in base agli indizi del linguaggio.
Alastair McCormack,

18
#!/usr/bin/env python
#-*- coding: utf-8 -*-
u = u'moçambique'
print u.encode("utf-8")
print u

chmod +x test.py
./test.py
moçambique
moçambique

./test.py > output.txt
Traceback (most recent call last):
  File "./test.py", line 5, in <module>
    print u
UnicodeEncodeError: 'ascii' codec can't encode character 
u'\xe7' in position 2: ordinal not in range(128)

su shell funziona, inviando a sdtout no, quindi questa è una soluzione alternativa, per scrivere su stdout.

Ho adottato un altro approccio, che non viene eseguito se sys.stdout.encoding non è definito, o in altre parole, è necessario prima esportare PYTHONIOENCODING = UTF-8 per scrivere su stdout.

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


quindi, usando lo stesso esempio:

export PYTHONIOENCODING=UTF-8
./test.py > output.txt

funzionerà


3
Questo non risponde alla domanda come richiesto. Piuttosto alcuni pensieri tangenziali sull'argomento.
ivan_pozdeev,

3
  • Il primo pericolo sta dentro reload(sys).

    Quando ricarichi un modulo, ottieni effettivamente due copie del modulo nel tuo runtime. Il vecchio modulo è un oggetto Python come tutto il resto, e rimane in vita finché ci sono riferimenti ad esso. Quindi, metà degli oggetti punta al vecchio modulo e metà a quello nuovo. Quando fai qualche cambiamento, non lo vedrai mai arrivare quando un oggetto casuale non vede il cambiamento:

    (This is IPython shell)
    
    In [1]: import sys
    
    In [2]: sys.stdout
    Out[2]: <colorama.ansitowin32.StreamWrapper at 0x3a2aac8>
    
    In [3]: reload(sys)
    <module 'sys' (built-in)>
    
    In [4]: sys.stdout
    Out[4]: <open file '<stdout>', mode 'w' at 0x00000000022E20C0>
    
    In [11]: import IPython.terminal
    
    In [14]: IPython.terminal.interactiveshell.sys.stdout
    Out[14]: <colorama.ansitowin32.StreamWrapper at 0x3a9aac8>
  • Ora, sys.setdefaultencoding()la corretta

    Tutto ciò che colpisce è la conversione implicitastr<->unicode . Ora, utf-8la codifica più sana del pianeta (retrocompatibile con ASCII e tutte), la conversione ora "funziona", cosa potrebbe andare storto?

    Bene, qualunque cosa. E questo è il pericolo.

    • Potrebbe esserci del codice che si basa sul UnicodeErrorlancio per input non ASCII o sulla transcodifica con un gestore di errori, che ora produce un risultato imprevisto. E poiché tutto il codice viene testato con le impostazioni predefinite, qui si è rigorosamente su territori "non supportati" e nessuno ti garantisce in che modo si comporterà il loro codice.
    • La transcodifica può produrre risultati imprevisti o inutilizzabili se non tutto sul sistema utilizza UTF-8 perché Python 2 in realtà ha più "codifiche di stringa predefinite" indipendenti . (Ricorda, un programma deve funzionare per il cliente, sull'attrezzatura del cliente.)
      • Ancora una volta, la cosa peggiore è che non lo saprai mai perché la conversione è implicita : non sai davvero quando e dove accadrà. (Python Zen, koan 2 ahoy!) Non saprai mai perché (e se) il tuo codice funziona su un sistema e si rompe su un altro. (O meglio ancora, funziona in IDE e si interrompe in console.)
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.