2012-02-10 35 views
14

Tôi nghĩ rằng tôi biết mọi thứ về mã hóa và Python, nhưng hôm nay tôi gặp một vấn đề lạ: mặc dù giao diện điều khiển được đặt thành mã trang 850 - và Python báo cáo chính xác - tham số tôi đặt trên dòng lệnh dường như được mã hóa trong trang mã 1252. Nếu tôi cố gắng giải mã chúng bằng sys.stdin.encoding, tôi nhận được kết quả sai. Nếu tôi giả sử 'cp1252', bỏ qua những gì sys.stdout.encoding báo cáo, nó hoạt động.Python, cửa sổ giao diện điều khiển và mã hóa (cp 850 vs cp1252)

Tôi có thiếu thứ gì đó hay đây là lỗi trong Python? Các cửa sổ ? Lưu ý: Tôi đang chạy Python 2.6.6 trên Windows 7 EN, ngôn ngữ được đặt thành tiếng Pháp (Thụy Sĩ).

Trong chương trình thử nghiệm dưới đây, tôi kiểm tra xem các chữ được giải thích chính xác và có thể được in hay không - tác phẩm này hoạt động. Nhưng tất cả các giá trị tôi vượt qua trên dòng lệnh dường như được mã hóa sai:

#!/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 "" 

Trong một giao diện điều khiển mới được tạo ra, khi chạy

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

tôi nhận được kết quả như sau

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 ] 

khi Tôi mong đợi ký tự thứ 4 có giá trị thứ tự là thay vì 233 (xem các trang mã 8501252).

Lưu ý: giá trị 128 cho biểu tượng đồng euro là một điều bí ẩn - vì cp850 không có. Nếu không '?' được mong đợi - cp850 không thể in các ký tự và tôi đã sử dụng 'thay thế' trong các chuyển đổi.

Nếu tôi thay đổi trang mã của giao diện điều khiển để 1252 bằng cách phát hành chcp 1252 và chạy lệnh tương tự, tôi (chính xác) được

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 

Bất kỳ ý tưởng những gì tôi đang thiếu?

Chỉnh sửa 1: Tôi vừa mới kiểm tra bằng cách đọc sys.stdin. Điều này làm việc như mong đợi: trong cp850, gõ 'é' kết quả trong một giá trị thứ tự của 130. Vì vậy, vấn đề là thực sự cho dòng lệnh chỉ. Vì vậy, là dòng lệnh xử lý khác với đầu vào tiêu chuẩn?

Chỉnh sửa 2: Có vẻ như tôi đã có từ khóa sai. Tôi đã tìm thấy một chủ đề rất gần khác về SO: Read Unicode characters from command-line arguments in Python 2.x on Windows. Tuy nhiên, nếu dòng lệnh không được mã hóa như sys.stdin, và vì sys.getdefaultencoding() báo cáo 'ascii', có vẻ như không có cách nào để biết mã hóa thực tế của nó. Tôi tìm thấy câu trả lời bằng cách sử dụng phần mở rộng win32 khá hacky.

Trả lời

21

Trả lời cho bản thân mình:

Trên Windows, mã hóa được sử dụng bởi giao diện điều khiển (như vậy, đó là sys.stdin ra/vào) khác với mã hóa khác nhau chuỗi OS-cung cấp - thu được thông qua ví dụ os.getenv(), sys.argv, và chắc chắn nhiều hơn nữa.

Mã hóa được cung cấp bởi sys.getdefaultencoding() thực sự là - mặc định, được chọn bởi nhà phát triển Python để khớp với "mã hóa hợp lý nhất" sử dụng trình thông dịch trong trường hợp cực đoan. Tôi nhận được 'ascii' trên Python 2.6 của tôi, và thử với Python 3.1 cầm tay, mang lại 'utf-8'. Cả hai đều không phải là những gì chúng ta đang tìm kiếm - chúng chỉ là những nhược điểm để mã hóa các hàm chuyển đổi.

Vì có vẻ như this page, mã hóa được các chuỗi do hệ điều hành cung cấp được điều chỉnh bởi Trang mã hoạt động (ACP). Kể từ khi Python không có một chức năng có nguồn gốc để lấy nó, tôi đã phải sử dụng ctypes:

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

Edit: Nhưng khi Jacek cho thấy, có thực sự là một cách mạnh mẽ hơn và Pythonic để làm điều đó (semantics sẽ cần xác nhận, nhưng cho đến khi chứng minh là sai, tôi sẽ sử dụng này)

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

và sau đó

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

Trên hệ thống của tôi, 01., vì vậy nó hoạt động. Tôi khá chắc chắn điều này sẽ phá vỡ trên các nền tảng khác, do đó, cảm thấy tự do để chỉnh sửa và làm cho nó chung chung hơn. Chúng tôi chắc chắn sẽ cần một số loại bảng dịch giữa ACP được báo cáo bởi Windows và tên mã hóa Python - một cái gì đó tốt hơn so với chỉ cần prepending 'cp'.

Thật không may là một hack, mặc dù tôi thấy nó ít xâm nhập hơn một đề xuất bởi this ActiveState Code Recipe (được liên kết bởi câu hỏi SO được đề cập trong Chỉnh sửa 2 của câu hỏi của tôi). Lợi thế tôi thấy ở đây là điều này có thể được áp dụng cho os.getenv(), và không chỉ cho sys.argv.

+2

Đối với Linux thường 'locale.getpreferredencoding()' hoặc, sau khi sử dụng 'locale.setlocale()' - 'locale.getlocale() [1]' cung cấp mã hóa đúng cho giao diện điều khiển và môi trường. Mặc dù, mã hóa UTF-8 thường đủ tốt cho hầu hết các hệ thống hiện đại (vì vậy nó là giá trị giảm tốt nhất). –

1

Tôi đã thử các giải pháp. Nó vẫn có thể có một số vấn đề về mã hóa. Chúng tôi cần sử dụng phông chữ loại thực. Khắc phục:

  1. Chạy chcp 65001 in cmd để thay đổi mã hóa thành UTF-8.
  2. Thay đổi cmd phông chữ cho một True-Loại một mặt hàng như Lucida Console có hỗ trợ các trang mã trước trước 65001

Dưới đây là sửa chữa hoàn chỉnh của tôi cho các lỗi mã hóa:

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

Note: Bạn có thể thấy một thay đổi phông chữ trong khi thực hiện chương trình.

Các vấn đề liên quan