60 votes

Changement d'encodage de cmd de Windows provoque un crash de Python

Tout d'abord, je change l'encodage de la fenêtre CMD de Windows en utf-8 et lance l'interpréteur Python :

chcp 65001
python

Ensuite, j'essaie d'imprimer une chaîne unicode à l'intérieur et lorsque je le fais, Python plante de manière particulière (je reçois simplement une invite cmd dans la même fenêtre).

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

Des idées sur la raison pour laquelle cela se produit et comment le faire fonctionner ?

MIS À JOUR : sys.stdin.encoding renvoie 'cp65001'

MIS À JOUR 2 : Il m'est simplement venu à l'esprit que le problème pourrait être lié au fait que l'utf-8 utilise un jeu de caractères multioctets (kcwu a fait un bon point à ce sujet). J'ai essayé de faire fonctionner tout l'exemple avec 'windows-1250' et j'ai obtenu 'ëeaî?'. Windows-1250 utilise un jeu de caractères unique donc cela a fonctionné pour les caractères qu'il comprend. Cependant, je suis toujours à court d'idées sur comment faire fonctionner 'utf-8' ici.

MIS À JOUR 3 : Oh, j'ai découvert que c'est un bug Python connu. Je suppose que ce qui se passe, c'est que Python copie l'encodage cmd en 'cp65001' dans sys.stdin.encoding et essaie de l'appliquer à toutes les entrées. Comme il échoue à comprendre 'cp65001', il plante sur toute entrée contenant des caractères non-ascii.

0 votes

Pouvez-vous imprimer sys.stdin.encoding? Qu'est-ce que cela renvoie?

5 votes

Il est facile pour python de savoir comment gérer le codec 'cp65001' : il suffit d'ajouter une ligne à Lib/encodings/aliases.py, faisant correspondre 'cp65001' à 'utf_8'. J'ai créé un correctif pour cela, et j'ai aussi mis à jour le bogue que vous mentionnez, Alex. Il reste cependant des problèmes.

0 votes

83voto

Daira Hopwood Points 687

Voici comment aliaser cp65001 en UTF-8 sans changer encodings\aliases.py :

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

(IMHO, ne prêtez pas attention à l'absurdité de l'histoire de l'Europe). cp65001 n'étant pas identique à UTF-8 à http://bugs.python.org/issue6058#msg97731 . Il est censé être le même, même si le codec de Microsoft présente quelques bogues mineurs).

Voici du code (écrit pour Tahoe-LAFS, tahoe-lafs.org) qui fait fonctionner la sortie console indépendamment de de la chcp et lit également les arguments de ligne de commande Unicode. Crédit à Michael Kaplan pour l'idée derrière cette solution. Si stdout ou stderr sont redirigés, il sortira UTF-8. Si vous voulez une marque d'ordre d'octet, vous devrez l'écrire explicitement.

[Edit : Cette version utilise WriteConsoleW au lieu de la _O_U8TEXT dans la bibliothèque d'exécution MSVC, qui est boguée. 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 Il est possible d'accéder au souhait de 'dejaVu Sans Mono, qui, je le reconnais, est une excellente police de caractères, pour la console.

Vous trouverez des informations sur les exigences en matière de polices et sur la manière d'ajouter de nouvelles polices pour la console Windows dans le manuel de l'utilisateur. Critères nécessaires pour que les polices soient disponibles dans une fenêtre de commande Microsoft KB

Mais en gros, sur Vista (probablement aussi Win7) :

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

Sur XP, consultez le fil de discussion Changer les polices de l'invite de commande' dans les forums de LockerGnome .

3 votes

+1 car votre réponse est digne, plus un +1 virtuel pour la suggestion de problème de police, même s'il est trop tard (moi et Windows avons rompu avec beaucoup de disputes; je ne pense pas que nous nous remettrons ensemble mais pour de brefs rencontres sur l'ordinateur de mes amis :) Merci.

2 votes

@David-Sarah : Merci pour le code très utile ! Savez-vous s'il existe un moyen correspondant de corriger la saisie dans la console (afin que les caractères Unicode copiés-collés fonctionnent correctement, indépendamment de la page de code, etc.) ? Cela impliquerait probablement l'utilisation de ReadConsoleW ?

0 votes

C'est possible, et en effet cela utiliserait ReadConsoleW. J'avais initialement prévu d'écrire ce code mais je n'utilise plus Windows depuis un certain temps maintenant. Si votre intérêt est pour Python 3, le bogue pertinent est bugs.python.org/issue1602, bien qu'il n'ait pas encore de solution pour l'entrée. (Un correctif pour ce bogue dépendrait des internes de Python 3 et ne serait pas facilement adaptable à Python 2.x.)

45voto

DenisKolodin Points 413

Définissez la variable système PYTHONIOENCODING :

> chcp 65001
> set PYTHONIOENCODING=utf-8
> python example.py
L'encodage est utf-8

Le code source de example.py est simple :

import sys
print "L'encodage est", sys.stdin.encoding

4 votes

J'ai essayé cela en Python 2.7.5 et bien que sys.stdin.encoding et sys.stdout.encoding aient tous deux indiqué utf-8, cela n'a pas généré la sortie appropriée. Il montrait chaque octet de sortie comme des caractères individuels au lieu de les combiner en points de code.

0 votes

python -c "import sys; print('Encodage='+sys.stdin.encoding)" au lieu de créer un fichier.

1 votes

Celle-ci a fonctionné pour moi sur Windows 7.0 x64. Dans mon cas, l'encodage était de 720.

2voto

tzot Points 32224

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

Prenez ce script (disons uniconsole.py dans votre site-packages 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 bug de Python (ou le bug de la console unicode de Win32, peu importe). Ensuite, j'ai ajouté tous les scripts associés :

try:
    import uniconsole
except ImportError:
    sys.exc_clear()  # pourrait être simplement pass, bien sûr
else:
    del uniconsole  # réduire la pollution, n'est plus nécessaire

Enfin, j'exécute simplement mes scripts au besoin dans une console où chcp 65001 est exécuté et la police est Lucida Console. (Comme j'aimerais que DejaVu Sans Mono puisse être utilisé à la place... mais pirater le registre et le sélectionner comme police de console se reconvertit en une police bitmap.)

Il s'agit d'un remplacement rapide et simple de 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.

0 votes

Cela fonctionne parfaitement! Ajoutez également au moins une balise vide def flush(self): pass à la classe pour qu'elle soit compatible avec stderr/stdout (il manque peut-être d'autres méthodes, mais Twisted n'a signalé que l'absence de .flush()).

0 votes

Après avoir utilisé votre extrait de code, il semble que l'extrait de code de David-Sarah Hopwood fonctionne de manière plus universelle.

0 votes

_DebuggerOutput n'a pas d'attribut fileno

2voto

jcoon Points 5693

Voulez-vous que Python encode en UTF-8 ?

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

Python ne reconnaîtra pas cp65001 comme UTF-8.

1voto

kcwu Points 2687

Cela est dû au fait que la "page de code" de cmd est différente du "mbcs" du système. Bien que vous ayez modifié la "page de code", Python (en fait, Windows) pense encore que votre "mbcs" n'a pas changé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X