web-dev-qa-db-fra.com

UnicodeEncodeError: le codec 'charmap' ne peut pas encoder - les mappages de caractères sur <undefined>, fonction d'impression

J'écris un programme Python (Python 3.3) pour envoyer des données à une page Web à l'aide de la méthode POST. Surtout pour le processus de débogage, j'obtiens le résultat de la page et l'affiche à l'écran à l'aide de la fonction print().

Le code est comme ça:

conn.request("POST", resource, params, headers)
response = conn.getresponse()
print(response.status, response.reason)
data = response.read()
print(data.decode('utf-8'));

la méthode HTTPResponse.read() renvoie un élément bytes codant la page (document UTF-8 bien formaté). Il semblait bien jusqu'à ce que j'arrête d'utiliser IDLE GUI for Windows et utilise la console Windows. au lieu. La page renvoyée a un caractère U + 2014 (em-dash) que la fonction d'impression traduit bien dans l'interface graphique de Windows (je présume que la page de code est Page 1252), mais pas dans la console Windows (page de code 850). Étant donné le comportement par défaut de strict, j'obtiens l'erreur suivante:

UnicodeEncodeError: 'charmap' codec can't encode character '\u2014' in position 10248: character maps to <undefined>

Je pourrais le réparer en utilisant ce code assez moche:

print(data.decode('utf-8').encode('cp850','replace').decode('cp850'))

Maintenant, il remplace le caractère incriminé "-" par un ?. Ce n'est pas le cas idéal (un trait d'union devrait être un meilleur remplaçant) mais suffisant pour mon propos.

Il y a plusieurs choses que je n'aime pas dans ma solution.

  1. Le code est moche avec tout ce décodage, encodage et décodage.
  2. Cela résout le problème pour ce cas seulement. Si je transfère le programme pour un système utilisant un autre codage (latin-1, cp437, retour à cp1252, etc.), il devrait reconnaître le codage cible. Ce ne est pas. (par exemple, lorsque vous utilisez à nouveau l'interface graphique IDLE, l'emdash est également perdu, ce qui n'était pas le cas auparavant)
  3. Ce serait mieux si emdash était traduit en trait d'union au lieu d'un coup d'interrogation.

Le problème n’est pas l’emdash (je peux penser à plusieurs façons de résoudre ce problème), mais j’ai besoin d’écrire du code robuste. Je nourris la page avec les données d'une base de données et ces données peuvent revenir. Je peux anticiper bien d'autres cas conflictuels: un 'Á' U + 00c1 (possible dans ma base de données) pourrait être traduit en CP-850 (codage de la console DOS/Windows pour les langues d'Europe occidentale) mais pas en CP-437 (codage pour les États-Unis). Anglais, qui est utilisé par défaut dans beaucoup d’installations Windows).

Alors, la question:

Existe-t-il une solution plus agréable qui rend mon code indépendant du codage de l'interface de sortie?

Je vois trois solutions à cela:

  1. Modifiez le codage de sortie afin qu’il génère toujours le format UTF-8. Voir par exemple Réglage de l'encodage correct lors du piping de la sortie standard en Python , mais je n'ai pas réussi à faire fonctionner ces exemples.

  2. L'exemple de code suivant permet à la sortie de connaître votre jeu de caractères cible.

    # -*- coding: utf-8 -*-
    import sys
    
    print sys.stdout.encoding
    print u"Stöcker".encode(sys.stdout.encoding, errors='replace')
    print u"Стоескер".encode(sys.stdout.encoding, errors='replace')
    

    Cet exemple remplace correctement tout caractère non imprimable de mon nom par un point d'interrogation.

    Si vous créez une fonction d’impression personnalisée, par exemple, appelé myprint, en utilisant ces mécanismes pour coder correctement la sortie, vous pouvez simplement remplacer print par myprint là où cela est nécessaire sans que tout le code ne paraisse moche.

  3. Réinitialisez l'encodage de sortie globalement au début du logiciel:

    La page http://www.macfreek.nl/memory/Encoding_of_Python_stdout présente un bon résumé de la procédure à suivre pour modifier le codage de sortie. La section "StreamWriter Wrapper around Stdout" est particulièrement intéressante. En gros, il est dit de changer la fonction d'encodage d'E/S comme ceci:

    Dans Python 2:

    if sys.stdout.encoding != 'cp850':
      sys.stdout = codecs.getwriter('cp850')(sys.stdout, 'strict')
    if sys.stderr.encoding != 'cp850':
      sys.stderr = codecs.getwriter('cp850')(sys.stderr, 'strict')
    

    Dans Python 3:

    if sys.stdout.encoding != 'cp850':
      sys.stdout = codecs.getwriter('cp850')(sys.stdout.buffer, 'strict')
    if sys.stderr.encoding != 'cp850':
      sys.stderr = codecs.getwriter('cp850')(sys.stderr.buffer, 'strict')
    

    Si utilisé dans la sortie HTML de CGI, vous pouvez remplacer "strict" par "xmlcharrefreplace" pour obtenir des balises codées HTML pour les caractères non imprimables.

    N'hésitez pas à modifier les approches, en définissant différents codages, .... Notez que cela ne fonctionnera toujours pas pour la sortie de données non spécifiées. Donc, toutes les données, les entrées, les textes doivent être correctement convertibles en unicode:

    # -*- coding: utf-8 -*-
    import sys
    import codecs
    sys.stdout = codecs.getwriter("iso-8859-1")(sys.stdout, 'xmlcharrefreplace')
    print u"Stöcker"                # works
    print "Stöcker".decode("utf-8") # works
    print "Stöcker"                 # fails
    
99
Dirk Stöcker

Basé sur la réponse de Dirk Stöcker, voici une fonction d’emballage soignée pour la fonction d’impression de Python 3. Utilisez-le comme vous utiliseriez l'impression.

En prime, comparé aux autres réponses, cela n'imprimera pas votre texte sous forme de repère ('b "contenu"'), mais sous forme de chaînes normales ("contenu"), en raison de la dernière étape de décodage.

def uprint(*objects, sep=' ', end='\n', file=sys.stdout):
    enc = file.encoding
    if enc == 'UTF-8':
        print(*objects, sep=sep, end=end, file=file)
    else:
        f = lambda obj: str(obj).encode(enc, errors='backslashreplace').decode(enc)
        print(*map(f, objects), sep=sep, end=end, file=file)

uprint('foo')
uprint(u'Antonín Dvořák')
uprint('foo', 'bar', u'Antonín Dvořák')
28
Jelle Fresen

Pour le débogage, vous pouvez utiliser print(repr(data)).

Pour afficher du texte, imprimez toujours Unicode. Ne codez pas le codage de caractères de votre environnement tel que cp850 dans votre script. Pour décoder la réponse http, voir n bon moyen d’obtenir le jeu de caractères/l’encodage d’une réponse HTTP en Python .

Pour imprimer la console Unicode sur Windows, vous pouvez tiliser le package win-unicode-console] .

25
jfs

J'ai creusé plus profondément dans ceci et trouvé les meilleures solutions sont ici.

http://blog.notdot.net/2010/07/Getting-unicode-right-in-Python

Dans mon cas, j'ai résolu "UnicodeEncodeError: le codec 'charmap' ne peut pas encoder de caractère"

code d'origine:

print("Process lines, file_name command_line %s\n"% command_line))

Nouveau code:

print("Process lines, file_name command_line %s\n"% command_line.encode('utf-8'))  
18
leemonq

Si vous utilisez la ligne de commande Windows pour imprimer les données, vous devez utiliser

chcp 65001

Cela a fonctionné pour moi!

15
Željko Krnjić

Si vous utilisez Python 3.6 (éventuellement 3.5 ou ultérieur), cette erreur ne me sera plus transmise. J'avais un problème similaire, car j'utilisais la v3.4, mais il est parti après ma désinstallation et ma réinstallation.

1
Solumyr