Imprima una cadena Unicode en Python independientemente del entorno

Estoy tratando de encontrar una solución genérica para imprimir cadenas Unicode desde una secuencia de comandos de Python.

Los requisitos son que debe ejecutarse en Python 2.7 y 3.x, en cualquier plataforma, y ​​con cualquier configuración de terminal y variables de entorno (por ejemplo, LANG = C o LANG = en_US.UTF-8).

La función de impresión de Python intenta automáticamente codificar la encoding del terminal al imprimir, pero si la encoding del terminal es ascii, falla.

Por ejemplo, lo siguiente funciona cuando el entorno “LANG = enUS.UTF-8”:

x = u'\xea' print(x) 

Pero falla en Python 2.7 cuando “LANG = C”:

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

Lo siguiente funciona independientemente de la configuración de LANG, pero no mostraría correctamente los caracteres Unicode si el terminal estuviera utilizando una encoding Unicode diferente:

 print(x.encode('utf-8')) 

El comportamiento deseado sería mostrar siempre unicode en el terminal si es posible y mostrar algo de encoding si el terminal no admite unicode. Por ejemplo, la salida estaría codificada en UTF-8 si el terminal solo admitiera ascii. Básicamente, el objective es hacer lo mismo que la función de impresión de Python cuando funciona, pero en los casos en los que la función de impresión falla, use alguna encoding predeterminada.

Puede manejar el caso LANG=C indicando a sys.stdout que sys.stdout de forma predeterminada a UTF-8 en los casos en que, de lo contrario, se establecería de forma predeterminada como ASCII.

 import sys, codecs if sys.stdout.encoding is None or sys.stdout.encoding == 'ANSI_X3.4-1968': utf8_writer = codecs.getwriter('UTF-8') if sys.version_info.major < 3: sys.stdout = utf8_writer(sys.stdout, errors='replace') else: sys.stdout = utf8_writer(sys.stdout.buffer, errors='replace') print(u'\N{snowman}') 

El fragmento de código anterior cumple con sus requisitos: funciona en Python 2.7 y 3.4, y no se rompe cuando LANG está en una configuración que no es UTF-8, como C

No es una técnica nueva , pero es sorprendentemente difícil de encontrar en la documentación. Como se presentó anteriormente, en realidad respeta configuraciones no UTF-8 como ISO 8859-* . Solo se establece de forma predeterminada en UTF-8 si Python hubiera configurado de forma predeterminada el ASCII, rompiendo la aplicación.

No creo que debas intentar resolver esto a nivel de Python. Documente los requisitos de su aplicación, registre la configuración regional de los sistemas que ejecuta para que pueda incluirse en los informes de errores y déjelo así.

Si desea ir por esta ruta, al menos distinga entre terminales y tuberías; nunca debe enviar datos a un terminal que el terminal no pueda manejar explícitamente; no emita UTF-8, por ejemplo, ya que los puntos de código no imprimibles> U + 007F podrían terminar siendo interpretados como códigos de control cuando se codifican.

Para una tubería, imprima UTF-8 por defecto y hágalo configurable.

Entonces, detectarías si se está utilizando un TTY y luego manejar la encoding en función de eso; para un terminal, establezca un controlador de errores (elija uno de replace o backslashreplace para proporcionar caracteres de reemplazo o secuencias de escape para cualquier carácter que no se pueda manejar). Para una tubería, use un codec configurable.

 import codecs import os import sys if os.istty(sys.stdout.fileno): output_encoding = sys.stdout.encoding errors = 'replace' else: output_encoding = 'utf-8' # allow override from settings errors = None # perhaps parse from settings, not needed for UTF8 sys.stdout = codecs.getwriter(output_encoding)(sys.stdout, errors=errors) 

Puede codificar la cadena usted mismo con el parámetro especial 'backslashreplace' para que los caracteres que no se pueden representar se conviertan en secuencias de escape. En Python 2 puede imprimir el resultado de la encode directamente, pero en Python 3 necesita decode nuevo a Unicode.

 import sys encoding = sys.stdout.encoding print(s.encode(encoding, 'backslashreplace').decode(encoding)) 

Si sys.stdout.encoding no entrega el valor que su terminal puede manejar, ese es un problema aparte con el que debe lidiar.

Puede manejar la excepción:

 def always_print(s): try: print(s) except UnicodeEncodeError: print(s.encode('utf-8'))