Python, консоль Windows и кодировки (cp 850 против cp1252) - PullRequest
16 голосов
/ 10 февраля 2012

Я думал, что знаю все о кодировках и Python, но сегодня я столкнулся со странной проблемой: хотя консоль настроена на кодовую страницу 850 - и Python сообщает об этом правильно - параметры, которые я помещаю в командную строку, похоже, закодированы вкодовая страница 1252. Если я пытаюсь декодировать их с помощью sys.stdin.encoding, я получаю неправильный результат.Если я предполагаю «cp1252», игнорируя то, что сообщает sys.stdout.encoding, это работает.

Я что-то упустил, или это ошибка в Python?Винда?Примечание: я использую Python 2.6.6 в Windows 7 EN, языковой стандарт установлен на французский (Швейцария).

В тестовой программе ниже я проверяю, правильно ли интерпретируются литералы и могут ли они быть напечатаны - это работает.Но все значения, которые я передаю в командной строке, похоже, неправильно закодированы:

#!/usr/bin/python
# -*- encoding: utf-8 -*-
import sys

literal_mb = 'utf-8 literal:   üèéÃÂç€ÈÚ'
literal_u = u'unicode literal: üèéÃÂç€ÈÚ'
print "Testing literals"
print literal_mb.decode('utf-8').encode(sys.stdout.encoding,'replace')
print literal_u.encode(sys.stdout.encoding,'replace')

print "Testing arguments ( stdin/out encodings:",sys.stdin.encoding,"/",sys.stdout.encoding,")"
for i in range(1,len(sys.argv)):
    arg = sys.argv[i]
    print "arg",i,":",arg
    for ch in arg:
        print "  ",ch,"->",ord(ch),
        if ord(ch)>=128 and sys.stdin.encoding == 'cp850':
            print "<-",ch.decode('cp1252').encode(sys.stdout.encoding,'replace'),"[assuming input was actually cp1252 ]"
        else:
            print ""

Во вновь созданной консоли при запуске

C:\dev>test-encoding.py abcé€

я получаю следующий вывод

Testing literals
utf-8 literal:   üèéÃÂç?ÈÚ
unicode literal: üèéÃÂç?ÈÚ
Testing arguments ( stdin/out encodings: cp850 / cp850 )
arg 1 : abcÚÇ
   a -> 97
   b -> 98
   c -> 99
   Ú -> 233 <- é [assuming input was actually cp1252 ]
   Ç -> 128 <- ? [assuming input was actually cp1252 ]

, хотя я ожидаю, что 4-й символ будет иметь порядковое значение 130 вместо 233 (см. Кодовые страницы 850 и 1252 ).

Примечания: значение 128 для символа евро является загадкой - поскольку у cp850 его нет.В противном случае, «?»ожидаются - cp850 не может печатать символы, и я использовал 'replace' в преобразованиях.

Если я изменил кодовую страницу консоли на 1252, введя chcp 1252 и выполнил ту же команду,Я (правильно) получаю

Testing literals
utf-8 literal:   üèéÃÂç€ÈÚ
unicode literal: üèéÃÂç€ÈÚ
Testing arguments ( stdin/out encodings: cp1252 / cp1252 )
arg 1 : abcé€
   a -> 97
   b -> 98
   c -> 99
   é -> 233
   € -> 128

Есть идеи, что мне не хватает?

Редактировать 1: Я только что проверил, прочитав sys.stdin.Это работает, как и ожидалось: в cp850 ввод 'é' приводит к порядковому значению 130. Таким образом, проблема действительно только для командной строки.Итак, командная строка обрабатывается иначе, чем стандартный ввод?

Редактировать 2: Кажется, у меня были неправильные ключевые слова.Я нашел еще одну очень близкую тему по SO: Чтение символов Unicode из аргументов командной строки в Python 2.x в Windows .Тем не менее, если командная строка не закодирована, как sys.stdin, и поскольку sys.getdefaultencoding () сообщает об «ascii», кажется, что нет способа узнать ее фактическую кодировку.Я нахожу ответ с помощью расширений win32 довольно странным.

Ответы [ 2 ]

23 голосов
/ 10 февраля 2012

Отвечая себе:

В Windows кодировка, используемая консолью (то есть sys.stdin / out), отличается от кодировки различных строк, предоставляемых ОС, получаемых, например, через. os.getenv (), sys.argv и, конечно, многие другие.

Кодировка, предоставляемая sys.getdefaultencoding (), действительно такова - по умолчанию, выбранная разработчиками Python для соответствия «наиболее разумной кодировке», которую использует интерпретатор в крайних случаях. Я получил 'ascii' на моем Python 2.6 и попробовал с переносным Python 3.1, который выдает 'utf-8'. И то, и другое - не то, что мы ищем - это просто запасные варианты для кодирования функций преобразования.

Поскольку эта страница , кажется, утверждает, что кодировка, используемая предоставленными ОС строками, регулируется активной кодовой страницей (ACP). Поскольку в Python нет встроенной функции для его извлечения, мне пришлось использовать ctypes:

from ctypes import cdll
os_encoding = 'cp' + str(cdll.kernel32.GetACP())

Редактировать: Но, как предполагает Яцек, на самом деле есть более надежный и Pythonic способ сделать это ( семантика потребует проверки, но пока не доказано, что я ошибаюсь, я буду использовать это )

import locale
os_encoding = locale.getpreferredencoding()
# This returns 'cp1252' on my system, yay!

, а затем

u_argv = [x.decode(os_encoding) for x in sys.argv]
u_env = os.getenv('myvar').decode(os_encoding)

В моей системе os_encoding = 'cp1252', так что это работает. Я совершенно уверен, что это сломается на других платформах, так что не стесняйтесь редактировать и делать его более общим. Нам, безусловно, понадобится какая-то таблица перевода между ACP, сообщаемым Windows, и именем кодировки Python - что-то лучше, чем просто добавление «cp».

Это, к сожалению, взлом, хотя я нахожу его немного менее навязчивым, чем тот, который предлагается в этом рецепте кода ActiveState (связан с вопросом SO, упомянутым в Edit 2 моего вопроса). Я вижу здесь преимущество в том, что это можно применить к os.getenv (), а не только к sys.argv.

1 голос
/ 11 декабря 2016

Я попробовал решения.У него все еще могут быть проблемы с кодировкой.Нам нужно использовать шрифты истинного типа.Исправление:

  1. Запустите chcp 65001 в cmd, чтобы изменить кодировку на UTF-8.
  2. Измените шрифт cmd на True-Type, например Lucida Console, который поддерживает предыдущие кодовые страницы перед65001

Вот мое полное исправление ошибки кодирования:

def fixCodePage():
    import sys
    import codecs
    import ctypes
    if sys.platform == 'win32':
        if sys.stdout.encoding != 'cp65001':
            os.system("echo off")
            os.system("chcp 65001") # Change active page code
            sys.stdout.write("\x1b[A") # Removes the output of chcp command
            sys.stdout.flush()
        LF_FACESIZE = 32
        STD_OUTPUT_HANDLE = -11
        class COORD(ctypes.Structure):
        _fields_ = [("X", ctypes.c_short), ("Y", ctypes.c_short)]

        class CONSOLE_FONT_INFOEX(ctypes.Structure):
            _fields_ = [("cbSize", ctypes.c_ulong),
            ("nFont", ctypes.c_ulong),
            ("dwFontSize", COORD),
            ("FontFamily", ctypes.c_uint),
            ("FontWeight", ctypes.c_uint),
            ("FaceName", ctypes.c_wchar * LF_FACESIZE)]

        font = CONSOLE_FONT_INFOEX()
        font.cbSize = ctypes.sizeof(CONSOLE_FONT_INFOEX)
        font.nFont = 12
        font.dwFontSize.X = 7
        font.dwFontSize.Y = 12
        font.FontFamily = 54
        font.FontWeight = 400
        font.FaceName = "Lucida Console"
        handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
        ctypes.windll.kernel32.SetCurrentConsoleFontEx(handle, ctypes.c_long(False), ctypes.pointer(font))

Примечание : Вы можете увидеть изменение шрифта при выполнении программы.

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