web-dev-qa-db-fra.com

Le changement d'encodage cmd Windows provoque un crash de Python

Tout d'abord, je modifie le codage Windows CMD en utf-8 et lance l'interpréteur Python:

chcp 65001
python

Ensuite, j'essaie d'imprimer une piqûre unicode à l'intérieur et quand je le fais, Python se bloque de manière particulière (je reçois juste une invite de commande dans la même fenêtre). 

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

Des idées pourquoi cela se produit et comment le faire fonctionner?

UPD: sys.stdin.encoding renvoie 'cp65001'

UPD2: Il m'est simplement apparu que le problème pouvait être lié au fait que utf-8 utilise un jeu de caractères multi-octets (kcwu a fait valoir un bon point à ce sujet). J'ai essayé d'exécuter tout l'exemple avec 'windows-1250' et j'ai obtenu '' un '?'. Windows-1250 utilise un jeu de caractères unique, de sorte qu'il fonctionne pour les caractères qu'il comprend. Cependant, je ne sais toujours pas comment faire fonctionner "utf-8" ici.

UPD3: Oh, j'ai découvert qu'il s'agit d'un bug connu de Python . Je suppose que ce qui se passe, c’est que Python copie le codage cmd en tant que 'cp65001 dans sys.stdin.encoding et tente de l’appliquer à toutes les entrées. Dans la mesure où il ne comprend pas 'cp65001', il se bloque sur toute entrée contenant des caractères non-ASCII.

55
Alex

Voici comment alias cp65001 en UTF-8 sans modifier encodings\aliases.py:

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

(À mon humble avis, ne faites pas attention à la sottise à propos de cp65001 n'étant pas identique à UTF-8 à l'adresse http://bugs.python.org/issue6058#msg97731 . Il est destiné à être le même, même si le codec de Microsoft a quelques bugs mineurs.)

Voici un code (écrit pour Tahoe-LAFS, tahoe-lafs.org) qui fait fonctionner la sortie de la console sans tenir compte de la page de code chcp , et lit également les arguments de ligne de commande Unicode. Nous remercions Michael Kaplan pour l'idée qui sous-tend cette solution. Si stdout ou stderr sont redirigés, il générera UTF-8. Si vous voulez une marque d'ordre d'octet, vous devrez l'écrire explicitement.

[Edit: Cette version utilise WriteConsoleW à la place de l'indicateur _O_U8TEXT dans la bibliothèque d'exécution MSVC, qui est un buggy. WriteConsoleW est également bogué par rapport à la documentation MS, mais moins.]

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

Enfin, il est possible d'accorder le souhait de ΩΤΖΙΟΥ d'utiliser DejaVu Sans Mono, qui, je le reconnais, est une excellente police, pour la console.

Vous pouvez trouver des informations sur les exigences en matière de polices et sur la procédure à suivre pour ajouter de nouvelles polices à la console Windows dans le 'Critères nécessaires pour que les polices soient disponibles dans une fenêtre de commande' Microsoft KB

Mais fondamentalement, sur Vista (probablement aussi Win7):

  • sous HKEY_LOCAL_MACHINE_SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont, définissez "0" sur "DejaVu Sans Mono";
  • pour chacune des sous-clés sous HKEY_CURRENT_USER\Console, définissez "FaceName" sur "DejaVu Sans Mono".

Sur XP, vérifiez le fil '' Changer les polices d'invite de commande? 'Dans les forums LockerGnome .

80
Daira Hopwood

Définissez_ PYTHONIOENCODINGvariable système:

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

La source de example.py est simple:

import sys
print "Encoding is", sys.stdin.encoding
42
DenisKolodin

J'avais aussi ce problème ennuyeux et je détestais ne pas pouvoir exécuter mes scripts compatibles Unicode de la même manière sous MS Windows que sous Linux. Donc, j'ai réussi à trouver une solution de contournement.

Prenez ce script (par exemple, uniconsole.py dans vos paquets de site ou autre):

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)

Cela semble contourner le bogue Python (ou le bogue de la console Win32 Unicode, peu importe). Puis j'ai ajouté dans tous les scripts liés:

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

Enfin, je lance simplement mes scripts selon les besoins dans une console où chcp 65001 est exécuté et la police est Lucida Console. (Comment je souhaite que DejaVu Sans Mono puisse être utilisé à la place… mais pirater le registre et le sélectionner comme police de la console rétablit une police bitmap.)

Ceci est un remplacement rapide stdout et stderr, et ne gère pas non plus les bogues liés à raw_input (évidemment, puisqu'il ne touche pas du tout à sys.stdin). Et, au fait, j'ai ajouté l'alias cp65001 pour utf_8 dans le fichier encodings\aliases.py de la bibliothèque standard.

3
tzot

Voulez-vous que Python soit encodé en UTF-8?

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

Python ne reconnaîtra pas cp65001 en tant que UTF-8. 

3
Jason Coon

En effet, "page de code" de cmd est différent de "mbcs" du système. Bien que vous ayez changé la "page de code", python (en fait, Windows) pense toujours que votre "mbcs" ne change pas.

2
kcwu

Quelques commentaires: vous avez probablement mal orthographié encodig et .code. Voici mon parcours de votre exemple.

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

La conclusion - cp65001 n'est pas un encodage connu pour python. Essayez 'UTF-16' ou quelque chose de similaire.

1
gimel

Pour le codage inconnu: cp65001 issue, peut définir la nouvelle Variable comme PYTHONIOENCODING et la Value comme UTF-8. (Cela fonctionne pour moi)

Voir ceci:
 View this

1
WenJun

Pour moi, définir cette variable env avant l'exécution du programme python a fonctionné:

set PYTHONIOENCODING=utf-8
1
damian1baran

Le problème a été résolu et traité dans ce fil de discussion:

Change l'encodage du système

La solution consiste à désélectionner l’Unicode UTF-8 pour une prise en charge mondiale sous Win. Cela nécessitera un redémarrage, sur lequel votre Python devrait être revenu à la normale. 

Étapes pour gagner:

  1. Aller au panneau de configuration
  2. Sélectionnez l'horloge et la région
  3. Cliquez sur Région> Administratif
  4. Dans Langue pour les programmes non Unicode, cliquez sur «Modifier les paramètres régionaux du système».
  5. Dans la fenêtre contextuelle "Paramètres de région", décochez "Bêta: Utiliser Unicode UTF-8 ..."
  6. Redémarrez la machine selon l'invite Win

L'image pour montrer l'emplacement exact de la façon de résoudre le problème:

Comment résoudre le problème

0
V C