Изменение кодировки Windows cmd вызывает сбой Python - PullRequest
55 голосов
/ 18 мая 2009

Сначала я изменяю кодировку 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 ]

80 голосов
/ 15 июля 2010

Вот как псевдоним 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#msg97731. Предполагается, что он будет таким же, даже если в кодеке 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
    # </825484/izmenenie-kodirovki-windows-cmd-vyzyvaet-sboi-python#825500>.
    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 .

42 голосов
/ 11 октября 2012

Set PYTHONIOENCODING системная переменная:

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

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

import sys
print "Encoding is", sys.stdin.encoding
3 голосов
/ 08 декабря 2017

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

Просмотр:
View this

3 голосов
/ 16 сентября 2009

У меня тоже была эта досадная проблема, и я ненавидел то, что не мог запускать свои сценарии с поддержкой юникода так же, как в 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 стандартной библиотеки lib.

3 голосов
/ 18 мая 2009

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

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

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

2 голосов
/ 18 мая 2009

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

1 голос
/ 23 апреля 2018

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

set PYTHONIOENCODING=utf-8
1 голос
/ 18 мая 2009

Несколько комментариев: вы, вероятно, ошиблись 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' или что-то подобное.

0 голосов
/ 22 января 2019

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

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

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

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

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

Картинка для точного определения места решения проблемы:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...