Establecer la codificación correcta al canalizar stdout en Python

343

Al canalizar la salida de un programa Python, el intérprete de Python se confunde acerca de la codificación y lo establece en Ninguno. Esto significa un programa como este:

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

funcionará bien cuando se ejecute normalmente, pero fallará con:

UnicodeEncodeError: el códec 'ascii' no puede codificar el carácter u '\ xa0' en la posición 0: el ordinal no está en el rango (128)

cuando se usa en una secuencia de tubería.

¿Cuál es la mejor manera de hacer que esto funcione al instalar tuberías? ¿Puedo decirle que use cualquier codificación del shell / sistema de archivos / lo que sea que esté usando?

Las sugerencias que he visto hasta ahora es modificar su site.py directamente, o codificar la codificación predeterminada utilizando este truco:

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

¿Hay una mejor manera de hacer que la tubería funcione?

Joakim Lundborg
fuente
1
Ver también stackoverflow.com/questions/4545661/…
ShreevatsaR
2
Si tiene este problema en Windows, también puede ejecutarlo chcp 65001antes de ejecutar su script. Esto puede tener problemas, pero a menudo ayuda, y no requiere mucho tipeo (menos de set PYTHONIOENCODING=utf_8).
Tomasz Gandor
El comando chcp no es lo mismo que configurar PYTHONIOENCODING. Creo que chcp es solo una configuración para el terminal en sí y no tiene nada que ver con escribir en un archivo (que es lo que estás haciendo al canalizar stdout). Intente setx PYTHONENCODING utf-8hacerlo permanente si desea guardar la escritura.
ejm
Enfrenté

Respuestas:

162

Su código funciona cuando se ejecuta en un script porque Python codifica la salida a cualquier codificación que esté utilizando su aplicación de terminal. Si está canalizando, debe codificarlo usted mismo.

Una regla general es: siempre use Unicode internamente. Decodifica lo que recibes y codifica lo que envías.

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

Otro ejemplo didáctico es un programa Python para convertir entre ISO-8859-1 y UTF-8, haciendo que todo esté en mayúscula.

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)

Establecer la codificación predeterminada del sistema es una mala idea, porque algunos módulos y bibliotecas que utiliza pueden confiar en el hecho de que es ASCII. No lo hagas

nosklo
fuente
11
El problema es que el usuario no quiere especificar la codificación explícitamente. Él solo quiere usar Unicode para IO. Y la codificación que usa debe ser una codificación especificada en la configuración regional, no en la configuración de la aplicación de terminal. AFAIK, Python 3 usa una codificación regional en este caso. Cambiar sys.stdoutparece una forma más placentera.
Andrey Vlasovskikh
44
La codificación / decodificación de cada cadena de manera explícita está destinada a causar errores cuando falta una llamada de codificación o decodificación o se agrega una vez a mucho en algún lugar. La codificación de salida se puede configurar cuando la salida es un terminal, por lo que se puede configurar cuando la salida no es un terminal. Incluso hay un entorno LC_CTYPE estándar para especificarlo. Es un pero en python que no respeta esto.
Rasmus Kaj
65
Esta respuesta es incorrecta. Usted debe no estar convirtiendo manualmente en cada entrada y salida de su programa; eso es frágil y completamente imposible de mantener.
Glenn Maynard el
29
@Glenn Maynard: ¿cuál es la respuesta correcta de IYO? Es más útil decirnos que simplemente decir 'Esta respuesta es incorrecta'
smci
14
@smci: la respuesta es no modificar su script, establezca PYTHONIOENCODINGsi está redirigiendo el stdout del script en Python 2.
jfs
168

Primero, con respecto a esta solución:

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

No es práctico imprimir explícitamente con una codificación determinada cada vez. Eso sería repetitivo y propenso a errores.

Una mejor solución es cambiar sys.stdoutal inicio de su programa, codificar con una codificación seleccionada. Aquí hay una solución que encontré en Python: ¿Cómo se elige sys.stdout.encoding? , en particular un comentario de "toka":

import sys
import codecs
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
Craig McQueen
fuente
77
desafortunadamente, cambiar sys.stdout para aceptar solo Unicode rompe muchas bibliotecas que esperan que acepte cadenas de bytes codificadas.
nosklo
66
nosklo: Entonces, ¿cómo puede funcionar de manera confiable y automática cuando la salida es un terminal?
Rasmus Kaj
3
@Rasmus Kaj: simplemente defina su propia función de impresión Unicode y úsela cada vez que desee imprimir Unicode: def myprint(unicodeobj): print unicodeobj.encode('utf-8')- automáticamente detecta la codificación del terminal mediante la inspección sys.stdout.encoding, pero debe considerar el caso en el que se encuentra None( es decir, al redirigir la salida a un archivo) entonces necesita una función separada de todos modos.
nosklo
3
@nosklo: Esto no hace que sys.stdout acepte solo Unicode. Puede pasar str y unicode a StreamWriter.
Glenn Maynard el
99
Supongo que esta respuesta estaba destinada a python2. Tenga cuidado con esto en el código que está destinado a admitir python2 y python3 . Para mí es romper cosas cuando se ejecuta bajo python3.
wim
130

Puede intentar cambiar la variable de entorno "PYTHONIOENCODING" a "utf_8". He escrito una página sobre mi terrible experiencia con este problema .

Tl; dr de la publicación 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))

te dio

utf_8
False
ANSI_X3.4-1968
ascii
utf_8
ö ☺ ☻
daveagp
fuente
2
Cambiando sys.stdout.encoding tal vez no funciona, pero cambiando sys.stdout funciona: sys.stdout = codecs.getwriter(encoding)(sys.stdout). Esto se puede hacer desde el programa python, por lo que el usuario no está obligado a establecer una variable env.
blueFast el
77
@ jeckyll2hide: PYTHONIOENCODINGfunciona. El entorno del usuario define cómo se interpretan los bytes como texto . Su secuencia de comandos no debe asumir y dictar al entorno del usuario qué codificación de caracteres usar. Si Python no recoge la configuración automáticamente, entonces PYTHONIOENCODINGse puede configurar para su script. No debería necesitarlo a menos que la salida se redirija a un archivo / tubería.
jfs
8
+1. Sinceramente, creo que es un error de Python. Cuando redirijo la salida, quiero esos mismos bytes que estarían en el terminal, pero en un archivo. Tal vez no sea para todos, pero es un buen defecto. Chocar duro sin explicación sobre una operación trivial que generalmente "simplemente funciona" es un mal defecto.
SnakE
@SnakE: la única forma en que puedo racionalizar por qué la implementación de Python forzaría intencionalmente una opción de codificación permanente y revestida de hierro en stdout en el momento del inicio, podría ser para evitar que cualquier material mal codificado salga más adelante. O cambiarlo es solo una característica no implementada, en cuyo caso permitir que el usuario la cambie más adelante sería una solicitud razonable de la función Python.
daveagp
2
@daveagp Lo que quiero decir es que el comportamiento de mi programa no debería depender de si se redirige o no, a menos que realmente lo quiera, en cuyo caso lo implemento yo mismo. Python se comporta en contra de mi experiencia con cualquier otra herramienta de consola. Esto viola el principio de menor sorpresa. Considero que esto es un defecto de diseño a menos que haya una justificación muy sólida.
SnakE
62
export PYTHONIOENCODING=utf-8

hacer el trabajo, pero no puedo configurarlo en Python en sí ...

lo que podemos hacer es verificar si no está configurando y decirle al usuario que lo configure antes del script de llamada 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)

Actualice para responder al comentario: el problema solo existe cuando se conecta a stdout. Probé en Fedora 25 Python 2.7.13

python --version
Python 2.7.13

gato b.py

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

print sys.stdout.encoding

corriendo ./b.py

UTF-8

ejecutando ./b.py | Menos

None
Sergio
fuente
2
Esa verificación no funciona en Python 2.7.13. sys.stdout.encodingse establece automáticamente en función del LC_CTYPEvalor local.
anfetamáquinas
1
mail.python.org/pipermail/python-list/2011-June/605938.html el ejemplo todavía funciona, es decir, cuando usa ./a.py> out.txt sys.stdout.encoding es None
Sérgio
Tuve un problema similar con un script de sincronización de Backblaze B2 y exportar PYTHONIOENCODING = utf-8 resolvió mi problema. Python 2.7 en Debian Stretch.
0x3333
5

Tuve un problema similar la semana pasada . Fue fácil de arreglar en mi IDE (PyCharm).

Aquí estaba mi solución:

A partir de la barra de menú de PyCharm: Archivo -> Configuración ... -> Editor -> Codificación de archivos, luego configure: "Codificación IDE", "Codificación de proyecto" y "Codificación predeterminada para archivos de propiedades" TODOS para UTF-8 y ahora funciona como un encanto.

¡Espero que esto ayude!

CLaFarge
fuente
4

Una versión desinfectada discutible de la respuesta de 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'ÅÄÖåäö'
Tompa
fuente
2

Podría "automatizarlo" con una llamada 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í, es posible obtener un bucle infinito aquí si este "setenv" falla.

jno
fuente
1
interesante, pero una pipa no parece estar contenta con esto
n611x007
2

Solo pensé en mencionar algo aquí con lo que tuve que pasar mucho tiempo experimentando antes de finalmente darme cuenta de lo que estaba pasando. Esto puede ser tan obvio para todos aquí que no se han molestado en mencionarlo. ¡Pero me hubiera ayudado si lo hubieran hecho, así que en ese principio ...!

NB: Estoy usando Jython específicamente, v 2.7, así que posiblemente esto no se aplique a CPython ...

NB2: las dos primeras líneas de mi archivo .py aquí son:

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

El mecanismo de construcción de cadena "%" (AKA "operador de interpolación") también causa problemas ADICIONALES ... Si la codificación predeterminada del "entorno" es ASCII e intenta hacer algo como

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

No tendrá dificultades para ejecutar en Eclipse ... En una CLI de Windows (ventana de DOS) encontrará que la codificación es la página de códigos 850 (mi sistema operativo Windows 7) o algo similar, que puede manejar caracteres con acento europeo al menos, por lo que trabajaré

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

También funcionará.

Si, OTOH, dirige a un archivo desde la CLI, la codificación stdout será None, que será ASCII (en mi sistema operativo de todos modos), que no podrá manejar ninguna de las impresiones anteriores ... (codificación temida error).

Entonces podría pensar en redirigir su stdout usando

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

e intente ejecutar en la tubería CLI a un archivo ... Muy extrañamente, la impresión A anterior funcionará ... ¡Pero la impresión B anterior arrojará el error de codificación! Sin embargo, lo siguiente funcionará bien:

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

La conclusión a la que he llegado (provisionalmente) es que si una cadena que se especifica como una cadena Unicode que utiliza el prefijo "u" se envía al mecanismo de manejo de%, parece implicar el uso de la codificación de entorno predeterminada, independientemente de si ha configurado stdout para redirigir!

La forma en que las personas lidian con esto es una cuestión de elección. Me gustaría que un experto en Unicode dijera por qué sucede esto, si me equivoqué de alguna manera, cuál es la solución preferida para esto, si también se aplica a CPython , si sucede en Python 3, etc., etc.

Mike roedor
fuente
Eso no es extraño, es porque "fréd"es una secuencia de bytes y no una cadena Unicode, por lo que el codecs.getwritercontenedor lo dejará solo. Necesita un líder u, o from __future__ import unicode_literals.
Matthias Urlichs
@MatthiasUrlichs OK ... gracias ... Pero acabo de encontrar la codificación de uno de los aspectos más irritantes de TI. ¿De dónde sacas tu comprensión? Por ejemplo, acabo de publicar otra pregunta sobre la codificación aquí: stackoverflow.com/questions/44483067/… : se trata de Java, Eclipse, Cygwin y Gradle. Si tu experiencia llega tan lejos, por favor ayuda ... ¡sobre todo me gustaría saber dónde obtener más información!
Mike roedor
1

Me encontré con este problema en una aplicación heredada, y fue difícil identificar dónde se imprimió. Me ayudé con este truco:

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

Además de mi script, test.py:

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

Tenga en cuenta que esto cambia TODAS las llamadas a imprimir para usar una codificación, por lo que su consola imprimirá esto:

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

En Windows, tuve este problema muy a menudo al ejecutar un código Python desde un editor (como Sublime Text), pero no si lo ejecuto desde la línea de comandos.

En este caso, verifique los parámetros de su editor. En el caso de SublimeText, esto lo Python.sublime-buildresolvió:

{
  "cmd": ["python", "-u", "$file"],
  "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
  "selector": "source.python",
  "encoding": "utf8",
  "env": {"PYTHONIOENCODING": "utf-8", "LANG": "en_US.UTF-8"}
}
Basj
fuente