2012-10-10 36 views
6

Cuando estoy escribiendo sysadmin en Python, el búfer en sys.stdout que afecta a cada llamada a print() es molesto, porque yo don No quiero esperar a que se vacíe un buffer y luego obtener una gran cantidad de líneas a la vez en la pantalla, en cambio quiero obtener líneas de salida individuales tan pronto como el script genere un nuevo resultado. Ni siquiera quiero esperar nuevas líneas para ver la salida.La expresión idiomática estándar de Python para establecer el búfer sys.stdout en cero no funciona con Unicode

Un lenguaje utilizado a menudo para hacer esto en Python es

import os 
import sys 
sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0) 

Esto funcionó bien para mí durante mucho tiempo. Ahora me di cuenta de que no funciona con Unicode. Consulte el siguiente guión:

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

from __future__ import print_function, unicode_literals 

import os 
import sys 

print('Original encoding: {}'.format(sys.stdout.encoding)) 
sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0) 
print('New encoding: {}'.format(sys.stdout.encoding)) 

text = b'Eisb\xe4r' 
print(type(text)) 
print(text) 

text = text.decode('latin-1') 
print(type(text)) 
print(text) 

Esto conduce al siguiente resultado:

Original encoding: UTF-8 
New encoding: None 
<type 'str'> 
Eisb▒r 
<type 'unicode'> 
Traceback (most recent call last): 
    File "./export_debug.py", line 18, in <module> 
    print(text) 
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe4' in position 4: ordinal not in range(128) 

Me tomó horas para rastrear la razón de ella (mi guión original era mucho más largo que este script mínima depuración) Es la línea

sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0) 

que utilicé durante años, así que no esperaba ningún problema con ella. Simplemente comente esta línea y la salida correcta debería verse así:

Original encoding: UTF-8 
New encoding: UTF-8 
<type 'str'> 
Eisb▒r 
<type 'unicode'> 
Eisbär 

Entonces, ¿qué es la secuencia de comandos para hacer? Para preparar mi código Python 2.7 lo más cerca posible a Python 3.x, siempre estoy usando

from __future__ import print_function, unicode_literals 

que hace Python utilizan la nueva impresión() - función pero lo más importante: Realiza tienda de Python como todas las cadenas Unicode internamente de forma predeterminada. Tengo una gran cantidad de datos codificados/ISO-8859-1-1 Latina, por ejemplo

text = b'Eisb\xe4r' 

para trabajar con él de la manera prevista, tengo que decodificarlo a Unicode en primer lugar, eso es lo que

text = text.decode('latin-1') 

es para. Como la codificación predeterminada es UTF-8 en mi sistema, cada vez que imprimo una cadena, python codifica la cadena interna Unicode a UTF-8. Pero primero tiene que estar en perfecto Unicode internamente.

Ahora todo funciona bien en general, pero no con un buffer de salida de cero bytes hasta el momento. ¿Algunas ideas? Me di cuenta de que sys.stdout.encoding no está configurado después de la línea de almacenamiento en cero cero, pero no sé cómo configurarlo de nuevo. Es un atributo de solo lectura y las variables de entorno del sistema operativo LC_ALL o LC_CTYPE parecen evaluarse solo al comienzo del intérprete de Python.

Btw .: 'Eisbär' es la palabra alemana para 'oso polar'.

+0

@martineau Bueno, la propuesta sys.stdout = codecs.getwriter ('utf8') (sys.stdout) tampoco funciona. Realmente probé y busqué mucho. Así que supongo que las ideas sin haberlas probado no ayudan mucho. –

+0

He migrado la pregunta por usted. La próxima vez, simplemente "marca" la atención del moderador y dinos lo que necesitas. :) – slhck

+0

@MartenLehmann: El hecho de que no fue probado es por qué lo publiqué como un comentario en lugar de una respuesta. – martineau

Respuesta

6

La función de impresión utiliza un indicador especial al escribir en un objeto de archivo, causando la función PyFile_WriteObject de la API de Python C para recuperar la codificación de salida para realizar la conversión de unicode a bytes y reemplazando la secuencia stdout que perdió la codificación Por desgracia, no se puede establecer explícitamente una vez más:

encoding = sys.stdout.encoding 
sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0) 
sys.stdout.encoding = encoding # Raises a TypeError; readonly attribute 

Tampoco se puede utilizar la io.open function lugar, ya que no permite el almacenamiento en búfer que se desactive si desea poder utilizar la opción encoding que le requiere.

La forma correcta de tener la función de descarga de impresión inmediatamente es utilizar el flush=True palabra clave:

print(something, flush=True) 

Si eso es demasiado tediosa para agregar todas partes, considere el uso de un encargo función de impresión:

def print(*args, **kw): 
    flush = kw.pop('flush', True) # Python 2.7 doesn't support the flush keyword.. 
    __builtins__.print(*args, **kw) 
    if flush: 
     sys.stdout.flush() 

Dado que la función print() de Python 2.7 no es realmente admite la palabra clave flush (botheration), puede simular que al agregar un color explícito en su lugar en esa versión personalizada.

+0

Puede reemplazar tres primeras líneas de su función personalizada 'print()' con esto: 'flush = kw.pop ('flush', True)'. – Tadeck

+0

@Tadeck: gran sugerencia, agregada. –

Cuestiones relacionadas