2009-05-18 31 views
50

Trước tiên tôi thay đổi mã hoá của Windows CMD để utf-8 và chạy Python thông dịch viên:của Windows thay đổi cmd mã hóa gây Python tai nạn

chcp 65001 
python 

Sau đó, tôi cố gắng in một sting unicode bên trong nó và khi tôi làm Python này bị treo trong một cách đặc biệt (tôi chỉ nhận được một dấu nhắc cmd trong cùng một cửa sổ).

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

Bất kỳ ý tưởng nào tại sao nó xảy ra và cách hoạt động?

UPD: sys.stdin.encoding lợi nhuận 'cp65001'

UPD2: Nó chỉ đến với tôi rằng vấn đề này có thể được kết nối với thực tế là utf-8 sử dụng multi-byte character set (kcwu làm một điểm tốt về việc này). Tôi đã thử chạy toàn bộ ví dụ với 'windows-1250' và có 'ëeaî?'. Windows-1250 sử dụng bộ ký tự đơn để nó hoạt động với những ký tự mà nó hiểu được. Tuy nhiên tôi vẫn không có ý tưởng làm thế nào để làm cho 'utf-8' làm việc ở đây.

UPD3: Ồ, tôi phát hiện ra nó là known Python bug. Tôi đoán những gì xảy ra là Python sao chép mã hóa cmd là 'cp65001 thành sys.stdin.encoding và cố gắng áp dụng nó cho tất cả các đầu vào. Vì nó không hiểu 'cp65001' nó treo trên bất kỳ đầu vào nào có chứa ký tự không phải ascii.

+0

bạn có thể in sys.stdin.encoding không? nó quay trở lại cái gì – nosklo

+0

Tôi trả về 'cp65001' – Alex

+5

Thật dễ dàng cho python biết cách xử lý codec 'cp65001': người ta phải thêm một dòng vào Lib/encodings/aliases.py, ánh xạ 'cp65001' thành 'utf_8'. Tôi đã tạo một bản vá cho điều đó và cũng cập nhật lỗi mà bạn đề cập đến, Alex. Tuy nhiên, vẫn có những vấn đề. – tzot

Trả lời

2

Điều này là do "trang mã" của cmd khác với "mbcs" của hệ thống. Mặc dù bạn đã thay đổi "trang mã", python (thực ra, các cửa sổ) vẫn nghĩ rằng "mbcs" của bạn không thay đổi.

1

Bạn có muốn Python mã hóa thành UTF-8 không?

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

Python sẽ không nhận ra cp65001 là UTF-8.

+0

Đó là giải pháp đơn giản để gỡ lỗi nhanh chóng. –

1

Một vài nhận xét: có thể bạn đã viết sai chính tả encodig.code. Đây là ví dụ của tôi.

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

Kết luận - cp65001 không phải là mã hóa đã biết cho trăn. Hãy thử 'UTF-16' hoặc một cái gì đó tương tự.

+0

Vâng, tôi chắc chắn đã viết sai chính tả, nhưng tôi đã thử nó đúng cách và cùng một sự cố (điều này chứng minh rằng người phiên dịch không thực sự đánh giá các thuộc tính 'mã hóa()' và 'mã hóa()' bị lỗi chính tả – Alex

3

Tôi cũng gặp vấn đề khó chịu này và tôi ghét không thể chạy các tập lệnh nhận dạng unicode giống như trong MS Windows như trong Linux. Vì vậy, tôi đã tìm cách giải quyết.

Đi kịch bản này (nói, uniconsole.py trong trang web gói hoặc bất cứ điều gì bạn):

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) 

Điều này dường như làm việc xung quanh trăn lỗi (hoặc win32 unicode console lỗi, bất cứ điều gì). Sau đó, tôi được thêm vào trong tất cả các kịch bản liên quan:

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

Cuối cùng, tôi chỉ cần chạy script của tôi khi cần thiết trong một giao diện điều khiển nơi chcp 65001 được điều hành và phông chữ là Lucida Console. (Làm thế nào tôi muốn rằng DejaVu Sans Mono có thể được sử dụng để thay thế ... nhưng hack registry và chọn nó như là một giao diện điều khiển phông chữ reverts vào một font bitmap.)

Đây là một cách nhanh chóng-và-bẩn stdoutstderr thay thế, và cũng không xử lý bất kỳ raw_input lỗi liên quan (rõ ràng, vì nó không chạm sys.stdin ở tất cả). Và, nhân tiện, tôi đã thêm bí danh cp65001 cho utf_8 vào tệp encodings\aliases.py của lib chuẩn.

+0

Điều này đang làm việc hoàn hảo! Ngoài ra, thêm ít nhất một 'xóa sạch (tự): bỏ qua' cho lớp để nó tương thích với 'stderr' /' stdout' (có thể là nhiều phương thức bị thiếu, nhưng Twisted chỉ phàn nàn về '.flush()' mất tích) –

+0

Sau khi đã sử dụng đoạn mã của bạn, trông giống như đoạn mã của David-Sarah Hopwood hoạt động phổ biến hơn. –

+0

_DebuggerOutput không có thuộc tính fileno – isarandi

76

Dưới đây là làm thế nào để bí danh cp65001 sang UTF-8 mà không thay đổi encodings\aliases.py:

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

(IMHO, không phải trả bất kỳ sự chú ý đến sự ngớ ngẩn về cp65001 không được giống với UTF-8 tại http://bugs.python.org/issue6058#msg97731 Nó dự định. để được như vậy, thậm chí nếu codec của Microsoft có một số lỗi nhỏ.)

Dưới đây là một số mã (bằng văn bản cho Tahoe-LAFS, tahoe-lafs.org) mà làm cho giao diện điều khiển đầu ra công việc bất của trang chcp mã, và cũng đọc Các đối số dòng lệnh Unicode. Ghi có vào Michael Kaplan cho ý tưởng đằng sau giải pháp này. Nếu stdout hoặc stderr được chuyển hướng, nó sẽ xuất UTF-8. Nếu bạn muốn có Dấu đơn hàng Byte, bạn sẽ cần phải viết nó một cách rõ ràng.

[Chỉnh sửa: Phiên bản này sử dụng WriteConsoleW thay vì cờ _O_U8TEXT trong thư viện thời gian chạy MSVC, lỗi. WriteConsoleW cũng là lỗi liên quan đến các tài liệu MS, nhưng ít như vậy.]

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

Cuối cùng, nó thể hiện điều ước ΤΖΩΤΖΙΟΥ để sử dụng DejaVu Sans Mono, mà tôi đồng ý là một phông chữ tuyệt vời, cho giao diện điều khiển .

Bạn có thể tìm thông tin về các yêu cầu phông chữ và làm thế nào để thêm phông chữ mới cho các cửa sổ giao diện điều khiển trong 'Necessary criteria for fonts to be available in a command window' Microsoft KB

Nhưng về cơ bản, trên Vista (có lẽ cũng Win7):

  • dưới HKEY_LOCAL_MACHINE_SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont, thiết lập "0" đến "DejaVu Sans Mono";
  • cho từng khóa con dưới HKEY_CURRENT_USER\Console, đặt "FaceName" thành "DejaVu Sans Mono".

Trên XP, hãy kiểm tra chủ đề 'Changing Command Prompt fonts?' in LockerGnome forums.

+3

+1 vì câu trả lời của bạn là xứng đáng, cộng với +1 ảo cho đề xuất về phông chữ, mặc dù đã quá muộn (tôi và Windows đã có nhiều đột phá, tôi không nghĩ chúng ta sẽ ở bên nhau nữa nhưng cuộc gặp gỡ ngắn gọn tại máy tính của bạn bè :) Cảm ơn. – tzot

+2

@ David-Sarah: Cảm ơn vì mã rất hữu ích! Bạn có biết rằng nếu có một cách tương ứng để sửa chữa giao diện điều khiển * đầu vào * (sao cho ví dụ: các ký tự unicode sao chép chỉ cần Làm việc, không phân biệt mã trang vv) Điều này có lẽ sẽ liên quan đến ReadConsoleW? –

+0

Điều đó là có thể, và thực sự nó sẽ sử dụng ReadConsoleW. Ban đầu tôi sẽ viết mã đó nhưng tôi đã không sử dụng Windows một thời gian. Nếu bạn quan tâm đến Python 3, lỗi có liên quan là http://bugs.python.org/issue1602, mặc dù nó chưa có giải pháp cho đầu vào. (Một bản vá cho lỗi đó sẽ phụ thuộc vào Python 3 internals và sẽ không dễ dàng thích nghi với Python 2.x.) –

36

Đặt PYTHONIOENCODING hệ thống biến:

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

Nguồn example.py rất đơn giản:

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

Và đừng quên đặt phông chữ đúng. – DenisKolodin

+2

Tôi đã thử điều này trong Python 2.7.5, và trong khi 'sys.stdin.encoding' và' sys.stdout.encoding' cả hai đều nói 'utf-8' nó không tạo ra kết quả thích hợp. Nó cho thấy mỗi byte đầu ra là ký tự riêng lẻ thay vì kết hợp chúng thành các điểm mã. –

+0

'python -c" nhập sys; in ('Encoding =' + sys.stdin.encoding) "' thay vì tạo một tệp. –

0

Đối biết mã hóa: Vấn đề cp65001, có thể thiết lập mới Biến như PYTHONIOENCODING và Value như UTF-8. (Điều này phù hợp với tôi)

View this

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