2009-05-18 17 views
50

Najpierw zmiany okna CMD kodowanie UTF-8 i uruchomić interpreter Pythona:Windows Zmień kodowanie cmd powoduje Pythona katastrofę

chcp 65001 
python 

Potem spróbuj wydrukować żądło unicode wewnątrz niego i kiedy to zrobić Pythona wywala w osobliwy sposób (po prostu pojawia się zachęta cmd w tym samym oknie).

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

Jakieś pomysły, dlaczego tak się dzieje i jak to działa?

UPD: sys.stdin.encoding powraca 'cp65001'

UPD2: To po prostu przyszedł do mnie, że problem może być związany z faktem, że UTF-8 korzysta multi-byte character set (kcwu to dobry punkt na tym). Próbowałem uruchomić cały przykład z "Windows-1250" i otrzymałem "Eee"? Windows-1250 używa zestawu jednoprzyciskowego, dzięki czemu zadziałał dla tych znaków, które rozumie. Jednak nadal nie mam pojęcia, jak zrobić tu pracę "utf-8".

UPD3: Och, dowiedziałem się, że jest to known Python bug. Domyślam się, że Python kopiuje kodowanie cmd jako "cp65001 do sys.stdin.encoding i próbuje zastosować to do wszystkich danych wejściowych. Ponieważ nie rozumie "cp65001", zawiesza się na każdym wejściu zawierającym znaki inne niż ascii.

+0

można drukować sys.stdin.encoding? co wraca? – nosklo

+0

Zwracam 'cp65001' – Alex

+5

Pythonowi łatwo jest wiedzieć, jak radzić sobie z kodekiem "cp65001": należy dodać linię do pliku Lib/encodings/aliases.py, mapując "cp65001" na "utf_8". Stworzyłem łatkę do tego, a także zaktualizowałem błąd, o którym wspomniałeś, Alex. Wciąż są jednak problemy. – tzot

Odpowiedz

2

Dzieje się tak, ponieważ "strona kodowa" cmd różni się od "mbcs" systemu. Chociaż zmieniłeś "stronę kodową", python (tak naprawdę, okna) nadal sądzi, że twoje "mbcs" się nie zmienia.

1

Czy chcesz, aby Python kodował do UTF-8?

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

Python nie rozpoznaje cp65001 jako UTF-8.

+0

To dobre proste rozwiązanie do szybkiego debugowania. –

1

Kilka uwag: prawdopodobnie błędnie wpisałeś encodig i .code. Oto mój bieg twojego przykładu.

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

Wnioski - cp65001 nie jest znane kodowanie pytona. Spróbuj "UTF-16" lub coś podobnego.

+0

Tak, zignorowałem to poprawnie, ale wypróbowałem to we właściwy sposób i przy tej samej awarii (to faktycznie udowodniło, że interpreter faktycznie nie mógł ocenić błędnych atrybutów "encode()" i "encoding()" i Przetwarzam "ëèæîð" Naprawiłem literówkę – Alex

3

Miałem również ten irytujący problem i nie cierpiałem nie móc uruchamiać moich skryptów obsługujących Unicode tak samo w MS Windows jak w Linuksie. Tak, udało mi się wymyślić obejście.

Weź ten skrypt (powiedzmy, uniconsole.py w swojej witryny opakowań lub cokolwiek):

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) 

Wydaje się to obejść bug (lub python unicode win32 konsoli błędów, cokolwiek). Potem dodaje się we wszystkich powiązanych skryptów:

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

Wreszcie, po prostu uruchomić moje skrypty, ile potrzeba w konsoli gdzie chcp 65001 jest prowadzony i czcionka jest Lucida Console. (Chciałbym, aby zamiast tego można było użyć DejaVu Sans Mono ... ale zhakowanie rejestru i wybranie go jako czcionki konsoli powraca do czcionki bitmapowej.)

Jest to szybki i-brudny stdout i stderr zastępczej, a także nie obsługuje żadnych raw_input podobnych błędów (oczywiście, ponieważ nie dotykać sys.stdin w ogóle). A tak przy okazji, dodałem alias cp65001 dla utf_8 w pliku encodings\aliases.py standardowej biblioteki.

+0

Działa to doskonale! Dodaj też co najmniej puste 'def flush (self): pass' do klasy, aby było kompatybilne z' stderr'/'stdout' (ewentualnie brakuje więcej metod, ale Twisted narzekał tylko na brak '.flush()'.) –

+0

Po użyciu twojego fragmentu, wygląda jak urywek Davida-Sarah Hopwood działa bardziej uniwersalnie –

+0

_DebuggerOutput nie ma atrybutu fileno – isarandi

76

Oto jak aliasu cp65001 na UTF-8 bez zmiany encodings\aliases.py:

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

(IMHO, nie zwracał uwagi na głupoty o cp65001 nie jest identyczna na UTF-8 w http://bugs.python.org/issue6058#msg97731 Jest on przeznaczony. być takie same, nawet jeśli kodek Microsoftu ma kilka drobnych błędów).

Oto kod (napisany dla Tahoe-LAFS, tahoe-lafs.org), który sprawia, że ​​praca wyjścia konsoli niezależnie strony kodowej chcp, a także czyta Argumenty wiersza poleceń Unicode. Podziękowania dla Michael Kaplan za pomysł rozwiązania tego problemu. Jeśli standardowe wyjście lub stderr zostaną przekierowane, wyświetli się kodowanie UTF-8. Jeśli chcesz uzyskać znak kolejności bajtów, musisz go wyraźnie zapisać.

[Edytuj: Ta wersja używa WriteConsoleW zamiast flagi _O_U8TEXT w bibliotece uruchomieniowej MSVC, która jest błędna. WriteConsoleW jest także buggy w stosunku do dokumentacji MS, ale w mniejszym stopniu.]

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 

Wreszcie jest możliwe przyznanie życzenie ΤΖΩΤΖΙΟΥ do korzystania DejaVu Sans Mono, którym się zgadzam to doskonały czcionki dla konsoli .

można znaleźć informacje o wymaganiach czcionek oraz jak dodać nowe czcionki dla konsoli Windows w 'Necessary criteria for fonts to be available in a command window' Microsoft KB

Ale zasadniczo, na Vista (prawdopodobnie także Win7):

  • pod HKEY_LOCAL_MACHINE_SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont ustaw "0" do "DejaVu Sans Mono";
  • dla każdego z podkluczy pod numerem HKEY_CURRENT_USER\Console, ustaw "FaceName" na "DejaVu Sans Mono".

W XP zaznacz wątek 'Changing Command Prompt fonts?' in LockerGnome forums.

+3

+1 ponieważ twoja odpowiedź brzmi godny, plus wirtualny +1 za sugestię dotyczącą fontów, mimo że jest za późno (ja i ​​Windows mieliśmy zerwanie z mnóstwem walk, nie sądzę, że kiedykolwiek będziemy razem, ale krótkie spotkania na komputerach znajomych :) Dzięki. – tzot

+2

@ David-Sarah: Dzięki za bardzo przydatny kod! Czy zdajesz sobie sprawę, czy istnieje odpowiednia metoda naprawy konsoli * input * (tak, że np. Kopiowane wklejone znaki Unicode po prostu działają, niezależnie od strony kodowej itp.) Prawdopodobnie obejmowałoby ReadConsoleW? –

+0

Jest to możliwe, a nawet użyłoby ReadConsoleW. Pierwotnie zamierzałem napisać ten kod, ale od jakiegoś czasu nie używam systemu Windows. Jeśli interesuje Cię Python 3, odpowiedni błąd to http://bugs.python.org/issue1602, chociaż nie ma jeszcze rozwiązania do wprowadzania danych. (Poprawka do tego błędu będzie zależeć od wewnętrznych elementów Pythona 3 i nie będzie łatwa do adaptacji do Pythona 2.x.) –

36

Ustaw PYTHONIOENCODING System zmienna:

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

Źródło example.py jest prosta:

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

I nie zapomnij ustawić poprawnej czcionki. – DenisKolodin

+2

Próbowałem tego w Pythonie 2.7.5, a podczas gdy 'sys.stdin.encoding' i' sys.stdout.encoding' zarówno powiedziały 'utf-8', nie wygenerował on poprawnego wyjścia. Pokazał każdy bajt wyjściowy jako indywidualne znaki zamiast łączyć je w punkty kodowe. –

+0

'python -c" importuj sys; print ('Encoding =' + sys.stdin.encoding) "' zamiast tworzyć plik. –

0

nieznanych kodowania: problem cp65001 można ustawić nową Variable jako PYTHONIOENCODING i Wartość jako UTF-8. (To działa na mnie)

View this

Powiązane problemy