Изменение кодировки Windows cmd вызывает сбой Python

Сначала я изменяю кодировку Windows CMD на utf-8 и запускаю интерпретатор Python:

chcp 65001
python

Затем я пытаюсь напечатать внутри него юникодную строку, и когда я делаю это, Python падает специфическим образом (я просто получаю приглашение cmd в том же окне).

>>> import sys
>>> print u'ëèæîð'.encode(sys.stdin.encoding)

Есть идеи, почему это происходит и как заставить это работать?

UPD: sys.stdin.encoding возвращается 'cp65001'

UPD2: Просто мне пришло в голову, что проблема может быть связана с тем фактом, что utf-8 использует многобайтовый набор символов (kcwu сделал это правильно). Я попытался запустить весь пример с "windows-1250" и получил "ea"? Windows-1250 использует односимвольный набор, поэтому он работает для тех символов, которые понимает. Однако я до сих пор не знаю, как заставить работать здесь utf-8.

UPD3: О, я узнал, что это известная ошибка Python. Я предполагаю, что происходит то, что Python копирует кодировку cmd как 'cp65001 в sys.stdin.encoding и пытается применить ее ко всем входным данным. Так как он не может понять 'cp65001', он падает на любом входе, который содержит символы не ascii.

9 ответов

Вот как псевдоним cp65001 до UTF-8 без изменения encodings\aliases.py:

import codecs
codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None)

(ИМХО, не обращай внимания на глупости про cp65001 не идентичен UTF-8 на http://bugs.python.org/issue6058. Он должен быть таким же, даже если в кодеке Microsoft есть незначительные ошибки.)

Вот некоторый код (написанный для Tahoe-LAFS, tahoe-lafs.org), который заставляет вывод консоли работать независимо от chcp кодовую страницу, а также читает аргументы командной строки Unicode. Благодарим Майкла Каплана за идею этого решения. Если stdout или stderr перенаправлены, он выдаст UTF-8. Если вам нужна метка порядка байтов, вам нужно написать ее явно.

[Редактировать: эта версия использует WriteConsoleW вместо _O_U8TEXT флаг в библиотеке времени выполнения MSVC, которая глючит. WriteConsoleW также глючит относительно документации MS, но не так.]

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://stackru.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

Наконец, можно удовлетворить желание ΤΖΩΤΖΙΟΥ использовать консоль DejaVu Sans Mono, которая, я согласен, является отличным шрифтом.

Информацию о требованиях к шрифтам и о том, как добавить новые шрифты для консоли Windows, можно найти в "Необходимых критериях доступности шрифтов в командном окне" Microsoft KB

Но в основном в Vista (возможно, также в Win7):

  • подHKEY_LOCAL_MACHINE_SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont, задавать"0"в "DejaVu Sans Mono";
  • для каждого из подразделов подHKEY_CURRENT_USER\Console, задавать"FaceName"в "DejaVu Sans Mono",

В XP проверьте тему "Изменение шрифтов командной строки?" в форумах LockerGnome.

Установите системную переменную PYTHONIOENCODING:

> chcp 65001
> set PYTHONIOENCODING=utf-8
> python example.py
Encoding is utf-8

Источник example.py это просто:

import sys
print "Encoding is", sys.stdin.encoding

У меня установка этого env var до выполнения программы на python работала:

set PYTHONIOENCODING=utf-8

У меня тоже была эта досадная проблема, и я ненавидел то, что не мог запускать свои сценарии с поддержкой юникода так же, как в MS Windows, так и в Linux. Итак, мне удалось придумать обходной путь.

Возьми этот сценарий (скажем, uniconsole.py в вашем сайте-пакеты или что-то еще):

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)

Похоже, это работает вокруг ошибки Python (или ошибки консоли Unicode в Win32, что угодно). Затем я добавил во все связанные сценарии:

try:
    import uniconsole
except ImportError:
    sys.exc_clear()  # could be just pass, of course
else:
    del uniconsole  # reduce pollution, not needed anymore

Наконец, я просто запускаю свои скрипты по мере необходимости в консоли, где chcp 65001 запускается и шрифт Lucida Console, (Как бы я этого хотел DejaVu Sans Mono вместо этого можно использовать... но взлом реестра и выбор его в качестве шрифта консоли превращается в растровый шрифт.)

Это быстро и грязно stdout а также stderr замена, а также не справляется с любой raw_input связанные ошибки (очевидно, так как это не касается sys.stdin совсем). И, кстати, я добавил cp65001 псевдоним для utf_8 в encodings\aliases.py файл стандартной библиотеки.

Вы хотите, чтобы Python кодировал в UTF-8?

>>>print u'ëèæîð'.encode('utf-8')
ëèæîð

Python не распознает cp65001 как UTF-8.

Для неизвестной кодировки: проблема cp65001, может установить новую переменную как PYTHONIOENCODING и значение как UTF-8. (Это работает для меня)

Посмотреть это

Это потому, что "кодовая страница" cmd отличается от "mbcs" системы. Несмотря на то, что вы изменили "кодовую страницу", python (фактически, windows) все еще думает, что ваши "mbcs" не меняются.

Начиная с Python 3.8+ кодировка cp65001 это псевдоним для utf-8

https://docs.python.org/library/codecs.html

Несколько комментариев: вы, вероятно, ошиблись encodig а также .code, Вот мой пример вашего примера.

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

Вывод - cp65001 не известная кодировка для python. Попробуйте "UTF-16" или что-то подобное.

Проблема была решена и решена в этой теме:

Изменить кодировку системы

Решение состоит в том, чтобы отменить выбор Unicode UTF-8 для всемирной поддержки в Win. Это потребует перезагрузки, после чего ваш Python должен вернуться в нормальное состояние.

Шаги для победы:

  1. Перейти к панели управления
  2. Выберите Часы и Регион
  3. Нажмите Регион> Административный
  4. В разделе "Язык" для программ, не поддерживающих Юникод, нажмите "Изменить язык системы".
  5. В появившемся окне "Настройки региона" снимите флажок "Бета: использовать Unicode UTF-8..."
  6. Перезагрузите компьютер в соответствии с приглашением Win

На картинке показано точное местоположение, как решить проблему:

Как решить проблему

Другие вопросы по тегам