caracteres - unicode en python 3
El cambio de codificación de cmd de Windows provoca la falla de Python (8)
¿Quieres que Python codifique para UTF-8?
>>>print u''ëèæîð''.encode(''utf-8'')
ëèæîð
Python no reconocerá cp65001 como UTF-8.
Primero cambio la codificación de Windows CMD a utf-8 y ejecuto el intérprete de Python:
chcp 65001
python
Luego trato de imprimir una picadura Unicode dentro y cuando lo hago, Python se cuelga de una manera peculiar (obtengo un prompt de cmd en la misma ventana).
>>> import sys
>>> print u''ëèæîð''.encode(sys.stdin.encoding)
¿Alguna idea de por qué sucede y cómo hacer que funcione?
UPD : sys.stdin.encoding
devuelve ''cp65001''
UPD2 : Me vino a la mente que el problema podría estar relacionado con el hecho de que utf-8 usa un juego de caracteres de múltiples bytes (kcwu hizo un buen punto al respecto). Intenté ejecutar todo el ejemplo con ''windows-1250'' y obtuve ''ëeaî''? ''. Windows-1250 usa un conjunto de caracteres únicos, por lo que funcionó para los personajes que comprende. Sin embargo, todavía no tengo idea de cómo hacer que ''utf-8'' funcione aquí.
UPD3 : Oh, descubrí que es un error conocido de Python . Supongo que lo que sucede es que Python copia la codificación cmd como ''cp65001 en sys.stdin.encoding e intenta aplicarla a toda la entrada. Como no comprende ''cp65001'', falla en cualquier entrada que contenga caracteres que no sean ascii.
A continuación, se cp65001
cómo alias cp65001
a UTF-8 sin cambiar las encodings/aliases.py
:
import codecs
codecs.register(lambda name: codecs.lookup(''utf-8'') if name == ''cp65001'' else None)
(En mi humilde opinión, no preste atención a las tonterías acerca de que cp65001
no sea idéntico a UTF-8 en http://bugs.python.org/issue6058#msg97731 . Está destinado a ser el mismo, incluso si el códec de Microsoft tiene algún inconveniente loco.)
Aquí hay un código (escrito para Tahoe-LAFS, tahoe-lafs.org) que hace que la salida de la consola funcione independientemente de la página de códigos chcp
, y también lee los argumentos de la línea de comandos Unicode. Gracias a Michael Kaplan por la idea detrás de esta solución. Si se redirigen stdout o stderr, se generará UTF-8. Si desea una Marca de orden de bytes, deberá escribirla explícitamente.
[Editar: Esta versión utiliza WriteConsoleW
lugar de la bandera _O_U8TEXT
en la biblioteca de tiempo de ejecución de _O_U8TEXT
, que tiene errores. WriteConsoleW
también tiene errores en relación con la documentación de MS, pero menos).
import sys
if sys.platform == "win32":
import codecs
from ctypes import WINFUNCTYPE, windll, POINTER, byref, c_int
from ctypes.wintypes import BOOL, HANDLE, DWORD, LPWSTR, LPCWSTR, LPVOID
original_stderr = sys.stderr
# If any exception occurs in this code, we''ll probably try to print it on stderr,
# which makes for frustrating debugging if stderr is directed to our wrapper.
# So be paranoid about catching errors and reporting them to original_stderr,
# so that we can at least see them.
def _complain(message):
print >>original_stderr, message if isinstance(message, str) else repr(message)
# Work around <http://bugs.python.org/issue6058>.
codecs.register(lambda name: codecs.lookup(''utf-8'') if name == ''cp65001'' else None)
# Make Unicode console output work independently of the current code page.
# This also fixes <http://bugs.python.org/issue1602>.
# Credit to Michael Kaplan <http://www.siao2.com/2010/04/07/9989346.aspx>
# and TZOmegaTZIOY
# <http://.com/questions/878972/windows-cmd-encoding-change-causes-python-crash/1432462#1432462>.
try:
# <http://msdn.microsoft.com/en-us/library/ms683231(VS.85).aspx>
# HANDLE WINAPI GetStdHandle(DWORD nStdHandle);
# returns INVALID_HANDLE_VALUE, NULL, or a valid handle
#
# <http://msdn.microsoft.com/en-us/library/aa364960(VS.85).aspx>
# DWORD WINAPI GetFileType(DWORD hFile);
#
# <http://msdn.microsoft.com/en-us/library/ms683167(VS.85).aspx>
# BOOL WINAPI GetConsoleMode(HANDLE hConsole, LPDWORD lpMode);
GetStdHandle = WINFUNCTYPE(HANDLE, DWORD)(("GetStdHandle", windll.kernel32))
STD_OUTPUT_HANDLE = DWORD(-11)
STD_ERROR_HANDLE = DWORD(-12)
GetFileType = WINFUNCTYPE(DWORD, DWORD)(("GetFileType", windll.kernel32))
FILE_TYPE_CHAR = 0x0002
FILE_TYPE_REMOTE = 0x8000
GetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, POINTER(DWORD))(("GetConsoleMode", windll.kernel32))
INVALID_HANDLE_VALUE = DWORD(-1).value
def not_a_console(handle):
if handle == INVALID_HANDLE_VALUE or handle is None:
return True
return ((GetFileType(handle) & ~FILE_TYPE_REMOTE) != FILE_TYPE_CHAR
or GetConsoleMode(handle, byref(DWORD())) == 0)
old_stdout_fileno = None
old_stderr_fileno = None
if hasattr(sys.stdout, ''fileno''):
old_stdout_fileno = sys.stdout.fileno()
if hasattr(sys.stderr, ''fileno''):
old_stderr_fileno = sys.stderr.fileno()
STDOUT_FILENO = 1
STDERR_FILENO = 2
real_stdout = (old_stdout_fileno == STDOUT_FILENO)
real_stderr = (old_stderr_fileno == STDERR_FILENO)
if real_stdout:
hStdout = GetStdHandle(STD_OUTPUT_HANDLE)
if not_a_console(hStdout):
real_stdout = False
if real_stderr:
hStderr = GetStdHandle(STD_ERROR_HANDLE)
if not_a_console(hStderr):
real_stderr = False
if real_stdout or real_stderr:
# BOOL WINAPI WriteConsoleW(HANDLE hOutput, LPWSTR lpBuffer, DWORD nChars,
# LPDWORD lpCharsWritten, LPVOID lpReserved);
WriteConsoleW = WINFUNCTYPE(BOOL, HANDLE, LPWSTR, DWORD, POINTER(DWORD), LPVOID)(("WriteConsoleW", windll.kernel32))
class UnicodeOutput:
def __init__(self, hConsole, stream, fileno, name):
self._hConsole = hConsole
self._stream = stream
self._fileno = fileno
self.closed = False
self.softspace = False
self.mode = ''w''
self.encoding = ''utf-8''
self.name = name
self.flush()
def isatty(self):
return False
def close(self):
# don''t really close the handle, that would only cause problems
self.closed = True
def fileno(self):
return self._fileno
def flush(self):
if self._hConsole is None:
try:
self._stream.flush()
except Exception as e:
_complain("%s.flush: %r from %r" % (self.name, e, self._stream))
raise
def write(self, text):
try:
if self._hConsole is None:
if isinstance(text, unicode):
text = text.encode(''utf-8'')
self._stream.write(text)
else:
if not isinstance(text, unicode):
text = str(text).decode(''utf-8'')
remaining = len(text)
while remaining:
n = DWORD(0)
# There is a shorter-than-documented limitation on the
# length of the string passed to WriteConsoleW (see
# <http://tahoe-lafs.org/trac/tahoe-lafs/ticket/1232>.
retval = WriteConsoleW(self._hConsole, text, min(remaining, 10000), byref(n), None)
if retval == 0 or n.value == 0:
raise IOError("WriteConsoleW returned %r, n.value = %r" % (retval, n.value))
remaining -= n.value
if not remaining:
break
text = text[n.value:]
except Exception as e:
_complain("%s.write: %r" % (self.name, e))
raise
def writelines(self, lines):
try:
for line in lines:
self.write(line)
except Exception as e:
_complain("%s.writelines: %r" % (self.name, e))
raise
if real_stdout:
sys.stdout = UnicodeOutput(hStdout, None, STDOUT_FILENO, ''<Unicode console stdout>'')
else:
sys.stdout = UnicodeOutput(None, sys.stdout, old_stdout_fileno, ''<Unicode redirected stdout>'')
if real_stderr:
sys.stderr = UnicodeOutput(hStderr, None, STDERR_FILENO, ''<Unicode console stderr>'')
else:
sys.stderr = UnicodeOutput(None, sys.stderr, old_stderr_fileno, ''<Unicode redirected stderr>'')
except Exception as e:
_complain("exception %r while fixing up sys.stdout and sys.stderr" % (e,))
# While we''re at it, let''s unmangle the command-line arguments:
# This works around <http://bugs.python.org/issue2128>.
GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32))
CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))(("CommandLineToArgvW", windll.shell32))
argc = c_int(0)
argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc))
argv = [argv_unicode[i].encode(''utf-8'') for i in xrange(0, argc.value)]
if not hasattr(sys, ''frozen''):
# If this is an executable produced by py2exe or bbfreeze, then it will
# have been invoked directly. Otherwise, unicode_argv[0] is the Python
# interpreter, so skip that.
argv = argv[1:]
# Also skip option arguments to the Python interpreter.
while len(argv) > 0:
arg = argv[0]
if not arg.startswith(u"-") or arg == u"-":
break
argv = argv[1:]
if arg == u''-m'':
# sys.argv[0] should really be the absolute path of the module source,
# but never mind
break
if arg == u''-c'':
argv[0] = u''-c''
break
# if you like:
sys.argv = argv
Finalmente, es posible otorgarle a ΤΖΩΤΖΙΟΥ el deseo de utilizar DejaVu Sans Mono, que acepto que es una excelente fuente para la consola.
Puede encontrar información sobre los requisitos de fuentes y cómo agregar nuevas fuentes para la consola de Windows en ''Criterios necesarios para que las fuentes estén disponibles en una ventana de comandos'' Microsoft KB
Pero básicamente, en Vista (probablemente también Win7):
- en
HKEY_LOCAL_MACHINE_SOFTWARE/Microsoft/Windows NT/CurrentVersion/Console/TrueTypeFont
, establezca"0"
en"DejaVu Sans Mono"
; - para cada una de las subclaves en
HKEY_CURRENT_USER/Console
, configure"FaceName"
en"DejaVu Sans Mono"
.
En XP, verifique el hilo ''¿Cómo cambiar las fuentes del símbolo del sistema?'' en los foros de LockerGnome .
Algunos comentarios: es probable que haya escrito mal encodig
y .code
. Aquí está mi carrera de tu ejemplo.
C:/>chcp 65001
Active code page: 65001
C:/>/python25/python
...
>>> import sys
>>> sys.stdin.encoding
''cp65001''
>>> s=u''/u0065/u0066''
>>> s
u''ef''
>>> s.encode(sys.stdin.encoding)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
LookupError: unknown encoding: cp65001
>>>
La conclusión: cp65001
no es una codificación conocida para python. Pruebe con ''UTF-16'' o algo similar.
Establecer la variable del sistema PYTHONIOENCODING :
> chcp 65001
> set PYTHONIOENCODING=utf-8
> python example.py
Encoding is utf-8
La fuente de example.py
es simple:
import sys
print "Encoding is", sys.stdin.encoding
Esto se debe a que la "página de códigos" de cmd es diferente a "mbcs" del sistema. Aunque cambió la "página de códigos", Python (en realidad, Windows) aún piensa que sus "mbcs" no cambian.
Para codificación desconocida: problema cp65001, puede establecer una nueva variable como PYTHONIOENCODING y un valor como UTF-8. (Esto funciona para mí)
Para mí configurar este env var antes de la ejecución del programa python funcionó:
set PYTHONIOENCODING=utf-8
También tuve este problema molesto, y odiaba no poder ejecutar mis scripts con reconocimiento unicode en MS Windows como en Linux. Entonces, logré encontrar una solución alternativa.
Tome esta secuencia de comandos (por ejemplo, uniconsole.py
en su sitio-paquetes o lo que sea):
import sys, os
if sys.platform == "win32":
class UniStream(object):
__slots__= ("fileno", "softspace",)
def __init__(self, fileobject):
self.fileno = fileobject.fileno()
self.softspace = False
def write(self, text):
os.write(self.fileno, text.encode("utf_8") if isinstance(text, unicode) else text)
sys.stdout = UniStream(sys.stdout)
sys.stderr = UniStream(sys.stderr)
Esto parece funcionar en torno a la falla de Python (o error de la consola win32 Unicode, lo que sea). Luego agregué en todos los scripts relacionados:
try:
import uniconsole
except ImportError:
sys.exc_clear() # could be just pass, of course
else:
del uniconsole # reduce pollution, not needed anymore
Finalmente, solo ejecuto mis scripts según sea necesario en una consola donde se ejecuta chcp 65001
y la fuente es Lucida Console
. (Cómo desearía que DejaVu Sans Mono
pudiera usar en su lugar ... pero piratear el registro y seleccionarlo como una fuente de consola revierte a una fuente de mapa de bits).
Este es un reemplazo de stdout
y stderr
rápido y sucio, y tampoco maneja ningún raw_input
relacionado con raw_input
(obviamente, ya que no toca sys.stdin
en absoluto). Y, dicho sea de paso, agregué el alias utf_8
para utf_8
en el archivo encodings/aliases.py
de la lib estándar.