¿Por qué NO deberíamos usar sys.setdefaultencoding ("utf-8") en un script py?

166

He visto algunos scripts py que usan esto en la parte superior del script. ¿En qué casos se debe usar?

import sys
reload(sys)
sys.setdefaultencoding("utf-8")
mlzboy
fuente
2
hay un problema con el uso de esto en ipython,% de tiempo deja de funcionar github.com/ipython/ipython/issues/8071
seanv507
3
@ seanv507, lea las respuestas - usarlo está seriamente desaconsejado
Alastair McCormack
2
¿Cómo es que esto no es un duplicado exacto de los peligros de sys.setdefaultencoding ('utf-8') ? ¿Aunque esta pregunta (2010) es anterior a esa (2015)? Pero esa pregunta también tiene buenas respuestas. ¿Qué hacer? Además, para ser claros, esta pregunta solo tiene sentido en Python 2 no 3, pero eso no está etiquetado ni mencionado en ninguna parte.
smci
vale la pena leer antes de sumergirse en SO respuestas: pythonhosted.org/kitchen/unicode-frustrations.html
ccpizza

Respuestas:

141

Según la documentación: esto le permite cambiar del ASCII predeterminado a otras codificaciones como UTF-8, que el tiempo de ejecución de Python usará siempre que tenga que decodificar un búfer de cadena para unicode.

Esta función solo está disponible en el momento de inicio de Python, cuando Python escanea el entorno. Debe llamarse en un módulo de todo el sistema sitecustomize.py. Después de evaluar este módulo, la setdefaultencoding()función se elimina del sysmódulo.

La única forma de usarlo es con un truco de recarga que recupera el atributo.

Además, el uso de sys.setdefaultencoding()siempre ha sido desaconsejado , y se ha convertido en un no-op en py3k. La codificación de py3k está conectada a "utf-8" y cambiarla genera un error.

Sugiero algunos consejos para leer:

pyfunc
fuente
66
Grandes cosas, aunque hay un poco de muerte por demasiada información aquí. Aprendí más centrándome en este artículo: blog.notdot.net/2010/07/Getting-unicode-right-in-Python
mbb
3
Me gustaría agregar que la codificación predeterminada también se usa para codificar (cuando se escribe sys.stdoutcuando tiene una Nonecodificación, como cuando se redirige la salida de un programa Python).
Eric O Lebigot
14
+1 para "el uso de sys.setdefaultencoding()siempre se ha desaconsejado"
jfs
77
'cableado a utf-8' no es cierto, no está cableado y no siempre es así UTF-8. LC_ALL=en_US.UTF-8 python3 -c 'import sys; print(sys.stdout.encoding)'da UTF-8pero LC_ALL=C python3 -c 'import sys; print(sys.stdout.encoding)'da ANSI_X3.4-1968(o tal vez otra cosa)
Tino
77
@Tino, la codificación de la consola está separada de la codificación predeterminada.
Alastair McCormack
59

tl; dr

¡La respuesta es NUNCA ! (a menos que realmente sepas lo que estás haciendo)

9/10 veces la solución puede resolverse con una comprensión adecuada de la codificación / decodificación.

1/10 personas tienen una configuración regional o entorno incorrectamente definidos y necesitan establecer:

PYTHONIOENCODING="UTF-8"  

en su entorno para solucionar problemas de impresión de la consola.

¿Qué hace?

sys.setdefaultencoding("utf-8")(tachado para evitar la reutilización) cambia la codificación / decodificación predeterminada utilizada cuando Python 2.x necesita convertir un Unicode () en un str () (y viceversa) y no se proporciona la codificación. Es decir:

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

En Python 2.x, la codificación predeterminada se establece en ASCII y los ejemplos anteriores fallarán con:

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

(Mi consola está configurada como UTF-8, entonces "€" = '\xe2\x82\xac', por lo tanto, la excepción está activada \xe2)

o

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

sys.setdefaultencoding("utf-8")permitirá que estos funcionen para , pero no necesariamente funcionarán para las personas que no usan UTF-8. El valor predeterminado de ASCII asegura que las suposiciones de codificación no se cuecen en el código

Consola

sys.setdefaultencoding("utf-8")También tiene el efecto secundario de aparecer en la reparación sys.stdout.encoding, que se usa al imprimir caracteres en la consola. Python usa la configuración regional del usuario (Linux / OS X / Un * x) o la página de códigos (Windows) para configurar esto. Ocasionalmente, la configuración regional de un usuario está rota y solo requiere PYTHONIOENCODINGcorregir la codificación de la consola .

Ejemplo:

$ 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"
€

¿Qué tiene de malo sys.setdefaultencoding ("utf-8") ?

La gente ha estado desarrollando contra Python 2.x durante 16 años en el entendimiento de que la codificación predeterminada es ASCII. UnicodeErrorSe han escrito métodos de manejo de excepciones para manejar conversiones de cadenas a Unicode en cadenas que se encuentran que no son ASCII.

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

Antes de establecer la codificación predeterminada, este código no podría decodificar el "Å" en la codificación ascii y luego ingresaría el controlador de excepciones para adivinar la codificación y convertirla correctamente en unicode. Impresión: Angstrom (Å®) dirige su negocio. Una vez que haya establecido la codificación predeterminada en utf-8, el código encontrará que byte_string se puede interpretar como utf-8 y, por lo tanto, destrozará los datos y devolverá esto: Angstrom (Ů) dirige su negocio.

Cambiar lo que debería ser una constante tendrá efectos dramáticos en los módulos de los que depende. Es mejor simplemente arreglar los datos que entran y salen de su código.

Problema de ejemplo

Si bien la configuración de la codificación predeterminada para UTF-8 no es la causa raíz en el siguiente ejemplo, muestra cómo se enmascaran los problemas y cómo, cuando cambia la codificación de entrada, el código se rompe de una manera no obvia: UnicodeDecodeError: el códec 'utf8' puede no decodifique el byte 0x80 en la posición 3131: byte de inicio no válido

Alastair McCormack
fuente
2
Si bien hay sorpresas sys.setdefaultencoding("utf-8"), es bueno hacer que el código se comporte más como Python 3. Ahora es 2017. Incluso cuando escribió la respuesta en 2015, creo que ya era mejor mirar hacia adelante en lugar de hacia atrás. En realidad, fue la solución más simple para mí, cuando descubrí que mi código se comportaba de manera diferente en Python 2 dependiendo de si la salida se redirige (problema muy desagradable para Python 2). No hace falta decir que ya lo hice # coding: utf-8, y no necesito ninguna solución para Python 3 (en realidad, tengo que enmascarar el setdefaultencodinguso de la verificación de versión).
Yongwei Wu
Eso es genial y funciona para usted, pero sys.setdefaultencoding("utf-8")no hace que su código Py 2.x sea compatible con Python 3. Tampoco repara módulos externos que asumen que la codificación predeterminada es ASCII. Hacer que su código sea compatible con Python 3 es muy simple y no requiere este truco desagradable. Por ejemplo, por qué esto causa problemas muy reales, vea mi experiencia con Amazon jugando con esta suposición: stackoverflow.com/questions/39465220/…
Alastair McCormack
1
@AlastairMcCormack rockea, Mi sitio ha estado desde hace meses y no podía entender qué hacer. Finalmente, PYTHONIOENCODING="UTF-8"ayudó a mi entorno Python2.7 Django-1.11. Gracias.
Sam
Sé que copiaste el ejemplo, pero puedo encontrar qué paquete tiene detect_encoding.
dlamblin
@dlamblin El ejemplo de código es para probar la cotización y no se debe usar en su código. Imagine que detect_encodinges un método que podría detectar la codificación de una cadena basada en pistas del lenguaje.
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)

en shell funciona, enviar a sdtout no, por lo que es una solución alternativa, escribir en stdout.

Hice otro enfoque, que no se ejecuta si sys.stdout.encoding no está definido, o en otras palabras, necesita exportar PYTHONIOENCODING = UTF-8 primero para escribir en 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)


entonces, usando el mismo ejemplo:

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

trabajará

Sergio
fuente
3
Esto no responde la pregunta como se le preguntó. Más bien algunos pensamientos tangenciales sobre el tema.
ivan_pozdeev
3
  • El primer peligro radica en reload(sys).

    Cuando vuelve a cargar un módulo, en realidad obtiene dos copias del módulo en su tiempo de ejecución. El antiguo módulo es un objeto Python como todo lo demás, y permanece vivo mientras haya referencias a él. Entonces, la mitad de los objetos apuntarán al módulo anterior y la otra mitad al nuevo. Cuando realice algún cambio, nunca lo verá venir cuando algún objeto aleatorio no vea el cambio:

    (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>
  • Ahora sys.setdefaultencoding()bien

    Todo lo que afecta es la conversión implícitastr<->unicode . Ahora, utf-8es la codificación más sensata del planeta (compatible con ASCII y todo lo anterior), la conversión ahora "simplemente funciona", ¿qué podría salir mal?

    Pues nada. Y ese es el peligro.

    • Puede haber algún código que se base en el UnicodeErrorlanzamiento de una entrada que no sea ASCII, o la transcodificación con un controlador de errores, que ahora produce un resultado inesperado. Y dado que todo el código se prueba con la configuración predeterminada, está estrictamente en territorio "no compatible" aquí , y nadie le ofrece garantías sobre cómo se comportará su código.
    • La transcodificación puede producir resultados inesperados o inutilizables si no todo en el sistema usa UTF-8 porque Python 2 en realidad tiene múltiples "codificaciones de cadena predeterminadas" independientes . (Recuerde, un programa debe funcionar para el cliente, en el equipo del cliente).
      • Una vez más, lo peor es que nunca lo sabrás porque la conversión es implícita ; realmente no sabes cuándo y dónde sucede. (Python Zen, koan 2 ahoy!) Nunca sabrá por qué (y si) su código funciona en un sistema y se rompe en otro. (O mejor aún, funciona en IDE y se rompe en la consola).
ivan_pozdeev
fuente