2012-02-10 2 views
14

Мне показалось, что я знаю все о кодировках и Python, но сегодня я столкнулся с какой-то странной проблемой: хотя консоль настроена на кодовую страницу 850 - и Python сообщает об этом правильно - параметры, введенные в командной строке, кажутся закодированными на кодовой странице 1252. Если я попытаюсь их декодировать с помощью sys.stdin.encoding, я получаю неправильный результат. Если я предполагаю 'cp1252', игнорируя то, что сообщает sys.stdout.encoding, он работает.Python, windows console и encodings (cp 850 vs cp1252)

Я что-то упустил или это ошибка в Python? Windows? Примечание. Я запускаю 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-го символа будет порядковое значение вместо 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: Read Unicode characters from command-line arguments in Python 2.x on Windows. Тем не менее, если командная строка не закодирована как sys.stdin, а так как sys.getdefaultencoding() сообщает «ascii», кажется, что нет способа узнать ее фактическую кодировку. Я нахожу ответ, используя расширения win32 довольно хаки.

ответ

21

Ответ на себе:

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

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

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

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

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

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

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

+2

Для Linux обычно 'locale.getpreferredencoding()' или, после использования 'locale.setlocale()' - 'locale.getlocale() [1]' дает правильную кодировку для доступа к консоли и среде. Хотя, жестко запрограммированный UTF-8 часто достаточно хорош для большинства современных систем (поэтому это лучшее значение возврата). –

1

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

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

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

Смежные вопросы