web-dev-qa-db-fra.com

Dangers du sys.setdefaultencoding ('utf-8')

Il existe une tendance à décourager le paramètre sys.setdefaultencoding('utf-8') dans Python 2. Quelqu'un peut-il répertorier de vrais exemples de problèmes avec cela? Des arguments comme it is harmful ou it hides bugs ne semble pas très convaincant.

UPDATE : Veuillez noter que cette question concerne uniquement utf-8, il ne s'agit pas de changer l'encodage par défaut "dans le cas général".

Veuillez donner quelques exemples avec du code si vous le pouvez.

33
anatoly techtonik

L'affiche originale demandait du code qui démontre que le commutateur est dangereux - sauf qu'il "cache" les bogues sans rapport avec le commutateur.

Résumé des conclusions

Sur la base de l'expérience et des preuves que j'ai recueillies, voici les conclusions auxquelles je suis arrivé.

  1. Définir le codage par défaut sur UTF-8 de nos jours est sûr , sauf pour les applications spécialisées, gérant les fichiers provenant de systèmes non compatibles Unicode.

  2. Le rejet "officiel" du commutateur est basé sur des raisons qui ne sont plus pertinentes pour la grande majorité des utilisateurs finaux (pas les fournisseurs de bibliothèques), nous devons donc arrêter décourager les utilisateurs de le définir.

  3. Travailler dans un modèle qui gère correctement Unicode par défaut est beaucoup mieux adapté aux applications pour les communications inter-systèmes que de travailler manuellement avec les API Unicode.

En effet, la modification très fréquente de l'encodage par défaut évite un certain nombre de maux de tête utilisateur dans la grande majorité des cas d'utilisation. Oui, il existe des situations dans lesquelles les programmes traitant de plusieurs encodages se comportent mal en silence, mais comme ce commutateur peut être activé au coup par coup, ce n'est pas un problème dans le code de l'utilisateur final .

Plus important encore, l'activation de cet indicateur est un véritable avantage est le code des utilisateurs, à la fois en réduisant les frais généraux liés à la gestion manuelle des conversions Unicode, en encombrant le code et en le rendant moins lisible, mais aussi en évitant bogues potentiels lorsque le programmeur ne parvient pas à le faire correctement dans tous les cas.


Étant donné que ces affirmations sont à peu près l'opposé exact de la ligne de communication officielle de Python, je pense qu'une explication de ces conclusions est justifiée.

Exemples d'utilisation réussie d'un encodage par défaut modifié dans la nature

  1. Dave Malcom de Fedora croyait que c'était toujours exact. Il a proposé, après avoir étudié les risques, de changer la distribution large def.enc. = UTF-8 pour tous Utilisateurs de Fedora.

    Le fait dur présenté cependant pourquoi Python briserait n'est que le comportement de hachage I répertorié , qui n'est jamais détecté par aucun autre adversaire dans le noyau communauté comme une raison de s'inquiéter ou même par le même personne , lorsque vous travaillez sur des billets d'utilisateur.

    CV de Fedora : Certes, le changement lui-même a été décrit comme "extrêmement impopulaire" par les développeurs principaux, et il a été accusé d'être incompatible avec les versions précédentes.

  2. Il y a 00 projets seuls sur openhub le font. Ils ont une interface de recherche lente, mais en scannant dessus, j'estime que 98% utilisent UTF-8. Rien trouvé sur les mauvaises surprises.

  3. Il y a 18000 (!) Branches master github avec cela changé.

    Alors que le changement est " impopulaire " dans la communauté principale, son joli populaire dans la base d'utilisateurs. Bien que cela puisse être ignoré, étant donné que les utilisateurs sont connus pour utiliser des solutions piratées, je ne pense pas que ce soit un argument pertinent en raison de mon point suivant.

  4. Il n'y a que 150 rapports de bogues total sur GitHub à cause de cela. À un taux effectivement de 100%, la variation semble être positive et non négative.

    Pour résumer les problèmes existants rencontrés par les gens -, j'ai parcouru tous les tickets susmentionnés.

    • Chaging def.enc. à UTF-8 est généralement introduit mais n'est pas supprimé dans le processus de clôture du problème, le plus souvent comme solution. Certains plus gros excusent comme correction temporaire , compte tenu de la "mauvaise presse" dont il dispose, mais beaucoup plus de rapporteurs de bogues sont justeheureux à propos - le correctif .

    • Quelques projets (1-5?) Ont modifié leur code en effectuant les conversions de type manuellement afin qu'ils n'aient plus besoin de changer la valeur par défaut.

    • Dans deux cas, je vois quelqu'un affirmer cela avec def.enc. réglé sur UTF-8 conduit à un manque complet de sortieentièrement , sans expliquer la configuration du test. Je n'ai pas pu vérifier la réclamation et j'en ai testé une et j'ai trouvé le contraire vrai.

    • Un prétend son "système" pourrait dépendre de ne pas le changer mais nous ne savons pas pourquoi.

    • Un (et seulement un) avait une vraie raison de l'éviter: ipython utilise un module tiers ou le testeur a modifié son processus de manière incontrôlée (il il n'est jamais contesté qu'un changement def.enc. est préconisé par ses partisans uniquement au moment de la configuration de l'interpréteur, c'est-à-dire lors de la "possession" du processus).

  5. Je n'ai trouvé aucune indication que les différents hachages de 'é' et u'é 'causent des problèmes dans le code du monde réel.

  6. Python fait pas "casse"

    Après avoir modifié le paramètre en UTF-8, aucune fonctionnalité de Python couverte par les tests unitaires ne fonctionne différemment que sans le commutateur. Le commutateur lui-même, cependant, n'est pas testé du tout.

  7. Il est conseillé sur bugs.python.org aux utilisateurs frustrés

    Exemples ici , ici ou ici (souvent lié à la ligne d'avertissement officielle)

    Le premier montre à quel point le commutateur est établi en Asie (comparer également avec l'argument github).

  8. Ian Bicking publié son support pour toujours permettant ce comportement.

    Je peux rendre mes systèmes et communications uniformément UTF-8, les choses iront mieux. Je ne vois vraiment aucun inconvénient. Mais pourquoi Python fait-il SO DAMN HARD [...] J'ai l'impression que quelqu'un a décidé qu'il était plus intelligent que moi, mais je ne suis pas sûr de le croire.

  9. Martijn Fassen, tout en réfutant Ian, admis que ASCII aurait pu se tromper en premier lieu.

    Je crois que si, par exemple, Python 2.5, livré avec un encodage par défaut UTF-8, il ne casserait rien. Mais si je le faisais pour mon Python, j'aurais des problèmes dès que je donnerais mon code à quelqu'un d'autre.

  10. En Python3, ils ne "pratiquent pas ce qu'ils prêchent"

    Tout en s'opposant à toute def.enc. changer si durement à cause du code dépendant de l'environnement ou de l'implicitude, une discussion ici tourne autour des problèmes de Python avec son 'sandwich unicode' paradigme et le hypothèses implicites requises correspondantes.

    De plus, ils ont créé des possibilités d'écrire du code Python3 valide comme:

    >>> from 褐褑褒褓褔褕褖褗褘 import *        
    >>> def 空手(合氣道): あいき(ど(合氣道))
    >>> 空手(う힑힜('???? ') + 흾)
    ????
    
  11. DiveIntoPython le recommande .

  12. Dans ce fil , Guido lui-même conseille a tilisateur final professionnel d'utiliser un environnement spécifique au processus avec le jeu de commutateurs pour "créer un Python pour chaque projet. "

    La raison fondamentale pour laquelle les concepteurs de la bibliothèque standard 2.x de Python ne veulent pas que vous puissiez définir le codage par défaut dans votre application, c'est que la bibliothèque standard est écrite en supposant que le codage par défaut est fixe et sans garantie le fonctionnement correct de la bibliothèque standard peut être effectué lorsque vous la modifiez. Il n'y a aucun test pour cette situation. Personne ne sait ce qui échouera quand. Et vous (ou pire, vos utilisateurs) reviendrez vers nous avec des plaintes si la bibliothèque standard commence soudainement à faire des choses auxquelles vous ne vous attendiez pas.

  13. Jython propose de le changer à la volée, même en modules.

  14. PyPy a pas supporté le rechargement (sys) - mais l'a ramené le demande de l'utilisateur en une seule journée sans poser de questions. Comparez avec l'attitude " vous vous trompez " de CPython, prétendant sans preuve, c'est la "racine du mal".


En terminant cette liste, je confirme que l'on pourrait construire un module qui plante à cause d'une configuration d'interpréteur modifiée, faisant quelque chose comme cette:

def is_clean_ascii(s):
    """ [Stupid] type agnostic checker if only ASCII chars are contained in s"""
    try:
        unicode(str(s))
        # we end here also for NON ascii if the def.enc. was changed
        return True
    except Exception, ex:
        return False    

if is_clean_ascii(mystr):
    <code relying on mystr to be ASCII>

Je ne pense pas que ce soit un argument valable car la personne qui a écrit ce module d'acceptation de type double était évidemment au courant des chaînes ASCII vs non ASCII et serait au courant de l'encodage et du décodage .

Je pense que cette preuve est plus que suffisante pour indiquer que la modification de ce paramètre ne conduit à aucun problème dans les bases de code du monde réel la vaste majorité du temps.

22
Red Pill

Parce que vous ne voulez pas toujours que vos chaînes soient automatiquement décodées en Unicode, ou d'ailleurs vos objets Unicode automatiquement encodés en octets. Puisque vous demandez un exemple concret, en voici un:

Prenez une application Web WSGI; vous créez une réponse en ajoutant le produit d'un processus externe à une liste, dans une boucle, et ce processus externe vous donne des octets encodés en UTF-8:

results = []
content_length = 0

for somevar in some_iterable:
    output = some_process_that_produces_utf8(somevar)
    content_length += len(output)
    results.append(output)

headers = {
    'Content-Length': str(content_length),
    'Content-Type': 'text/html; charset=utf8',
}
start_response(200, headers)
return results

C'est très bien et ça marche. Mais votre collègue arrive et ajoute une nouvelle fonctionnalité; vous fournissez maintenant également des étiquettes, qui sont localisées:

results = []
content_length = 0

for somevar in some_iterable:
    label = translations.get_label(somevar)
    output = some_process_that_produces_utf8(somevar)

    content_length += len(label) + len(output) + 1
    results.append(label + '\n')
    results.append(output)

headers = {
    'Content-Length': str(content_length),
    'Content-Type': 'text/html; charset=utf8',
}
start_response(200, headers)
return results

Vous avez testé cela en anglais et tout fonctionne toujours, super!

Cependant, la bibliothèque translations.get_label() renvoie en fait valeurs Unicode et lorsque vous changez de locale, les étiquettes contiennent des caractères non ASCII.

La bibliothèque WSGI écrit ces résultats dans le socket, et toutes les valeurs Unicode sont automatiquement codées pour vous, car vous définissez setdefaultencoding() sur UTF-8, mais la longueur vous avez calculé est tout à fait faux . Ce sera trop court car UTF-8 code tout en dehors de la plage ASCII avec plus d'un octet.

Tout cela ignore la possibilité que vous travaillez réellement avec des données dans un codec différent; vous pourriez écrire Latin-1 + Unicode, et maintenant vous avez un en-tête de longueur incorrect et un mélange d'encodages de données.

Si vous n'aviez pas utilisé sys.setdefaultencoding() une exception aurait été levée et vous saviez que vous aviez un bug, mais maintenant vos clients se plaignent de réponses incomplètes; il y a des octets manquants à la fin de la page et vous ne savez pas comment cela s'est produit.

Notez que ce scénario n'implique même pas des bibliothèques tierces qui peuvent ou non dépendre de la valeur par défaut toujours ASCII. Le paramètre sys.setdefaultencoding() est global , s'appliquant au code all exécuté dans l'interpréteur. Dans quelle mesure êtes-vous sûr qu'il n'y a aucun problème dans ces bibliothèques impliquant un codage ou un décodage implicite?

Ce Python 2 code et décode entre les types str et unicode implicitement peut être utile et sûr lorsque vous traitez avec ASCII = données uniquement. Mais vous avez vraiment besoin de savoir lorsque vous mélangez accidentellement des données de chaîne Unicode et d'octets, plutôt que de les recouvrir d'un pinceau global et d'espérer pour le meilleur.

16
Martijn Pieters

Tout d'abord: de nombreux opposants à la modification de l'enc par défaut soutiennent que son idiot parce que son même en changeant les comparaisons ascii

Je pense qu'il est juste de préciser que, conformément à la question initiale, je ne vois personne préconiser autre chose que de s'écarter d'Ascii UTF-8.

L'exemple setdefaultencoding ('utf-16') semble toujours être simplement proposé par ceux qui s'opposent à sa modification ;-)


Avec m = {'a': 1, 'é': 2} et le fichier 'out.py':

# coding: utf-8
print u'é' 

Alors:

+---------------+-----------------------+-----------------+
| DEF.ENC       | OPERATION             | RESULT (printed)|            
+---------------+-----------------------+-----------------+
| ANY           | u'abc' == 'abc'       | True            |     
| (i.e.Ascii    | str(u'abc')           | 'abc'           |
|  or UTF-8)    | '%s %s' % ('a', u'a') | u'a a'          | 
|               | python out.py         | é               |
|               | u'a' in m             | True            |
|               | len(u'a'), len(a)     | (1, 1)          |
|               | len(u'é'), len('é')   | (1, 2) [*]      |
|               | u'é' in m             | False  (!)      |
+---------------+-----------------------+-----------------+
| UTF-8         | u'abé' == 'abé'       | True   [*]      |
|               | str(u'é')             | 'é'             |
|               | '%s %s' % ('é', u'é') | u'é é'          | 
|               | python out.py | more  | 'é'             |
+---------------+-----------------------+-----------------+
| Ascii         | u'abé' == 'abé'       | False, Warning  |
|               | str(u'é')             | Encoding Crash  |
|               | '%s %s' % ('é', u'é') | Decoding Crash  |
|               | python out.py | more  | Encoding Crash  |
+---------------+-----------------------+-----------------+

[*]: Le résultat suppose que pareil é. Voir ci-dessous à ce sujet.

En examinant ces opérations, changer le codage par défaut dans votre programme pourrait ne pas sembler trop mauvais, vous donnant des résultats plus proches d'avoir des données Ascii uniquement.

En ce qui concerne le comportement de hachage (in) et len ​​(), vous obtenez le même que dans Ascii (plus sur les résultats ci-dessous). Ces opérations montrent également qu'il existe des différences importantes entre les chaînes unicode et les chaînes d'octets - qui peuvent provoquer des erreurs logiques si vous les ignorez.

Comme indiqué précédemment: il s'agit d'une option à l'échelle du processus , vous n'avez donc qu'une seule photo à choisir - c'est la raison pour laquelle bibliothèque les développeurs ne devraient jamais le faire, mais ils doivent mettre de l'ordre dans leurs composants internes afin qu'ils n'aient pas besoin de s'appuyer sur les conversions implicites de python. Ils doivent également documenter clairement ce qu'ils attendent et retourner et refuser l'entrée pour laquelle ils n'ont pas écrit la bibliothèque (comme la fonction de normalisation, voir ci-dessous).

=> L'écriture de programmes avec ce paramètre activé risque pour les autres d'utiliser les modules de votre programme dans leur code, au moins sans filtrer les entrées.

Remarque: Certains adversaires prétendent que def.enc. est même une option à l'échelle du système (via sitecustomize.py), mais le dernier en date de la conteneurisation des logiciels (docker), chaque processus peut être démarré dans son environnement parfait sans frais généraux.


Concernant le comportement de hachage et len ​​():

Il vous indique que même avec un def.enc modifié. vous ne pouvez toujours pas ignorer les types de chaînes que vous traitez dans votre programme. u '' et '' sont des séquences d'octets différentes dans la mémoire - pas toujours mais en général.

Ainsi, lors des tests, assurez-vous que votre programme se comporte correctement également avec les données non Ascii.

Certains disent que le fait que les hachages peuvent devenir inégaux lorsque les valeurs des données changent - bien qu'en raison de conversions implicites les opérations '==' restent égales - est un argument contre le changement de def.enc.

Personnellement, je ne partage pas cela, car le comportement de hachage reste le même que sans le changer. Je n'ai pas encore vu d'exemple convaincant de comportement indésirable en raison de cette configuration dans un processus que je "possède".

Dans l'ensemble, concernant setdefaultencoding ("utf-8"): La réponse quant à savoir si son idiot ou non devrait être plus équilibré.

Ça dépend. Bien qu'il évite les plantages, par exemple aux opérations str () dans une instruction log - le prix est plus élevé pour des résultats inattendus plus tard, car les mauvais types le rendent plus long en code dont le bon fonctionnement dépend d'un certain type.

En aucun cas, cela ne devrait être l'alternative à l'apprentissage de la différence entre les chaînes d'octets et les chaînes unicode pour votre propre code.


Enfin, définir l'encodage par défaut loin d'Ascii ne vous facilite pas la vie pour les opérations de texte courantes comme len (), le découpage et les comparaisons - si vous supposez que (octet) tout en stringyfiant avec UTF-8 sur résout les problèmes ici.

Malheureusement, ce n'est pas le cas - en général.

Les résultats '==' et len ​​() sont loin un problème plus complexe qu'on pourrait le penser - mais même avec le même type des deux côtés.

Sans def.enc. changé, "==" échoue toujours pour les non Ascii, comme indiqué dans le tableau. Avec ça, ça marche - parfois:

Unicode a normalisé environ un million de symboles du monde et leur a donné un nombre - mais il n'y a malheureusement PAS de bijection 1: 1 entre les glyphes affichés à un utilisateur dans les périphériques de sortie et les symboles à partir desquels ils sont générés.

Pour vous motiver recherchez ceci : Avoir deux fichiers, j1, j2 écrits avec le programme même utilisant le même encodage, contenant une entrée utilisateur:

>>> u1, u2 = open('j1').read(), open('j2').read()
>>> print sys.version.split()[0], u1, u2, u1 == u2

Résultat: 2.7.9 José José False (!)

En utilisant l'impression comme une fonction dans Py2, vous voyez la raison: Malheureusement, il existe DEUX façons d'encoder le même caractère, le "e" accentué:

>>> print (sys.version.split()[0], u1, u2, u1 == u2)
('2.7.9', 'Jos\xc3\xa9', 'Jose\xcc\x81', False)

Quel stupide codec vous pourriez dire, mais ce n'est pas la faute du codec. C'est un problème en Unicode en tant que tel.

Donc, même dans Py3:

>>> u1, u2 = open('j1').read(), open('j2').read()
>>> print sys.version.split()[0], u1, u2, u1 == u2

Résultat: 3.4.2 José José False (!)

=> Indépendant de Py2 et Py3, en fait indépendant de tout langage informatique que vous utilisez: Pour écrire un logiciel de qualité, vous devez probablement "normaliser" toutes les entrées utilisateur. La norme unicode a normalisé la normalisation. Dans Python 2 et 3, la fonction unicodedata.normalize est votre amie.

3
Red Pill

Exemple réel n ° 1

Cela ne fonctionne pas dans tests unitaires.

Le lanceur de test (nose, py.test, ...) initialise sys d'abord, puis découvre et importe vos modules. À ce moment-là, il est trop tard pour changer l'encodage par défaut.

Par la même vertu, cela ne fonctionne pas si quelqu'un exécute votre code en tant que module, car leur initialisation vient en premier.

Et oui, mélanger str et unicode et s'appuyer sur une conversion implicite ne fait que pousser le problème plus loin.

2
Dima Tisnek

Une chose que nous devons savoir est

Python 2 utilise sys.getdefaultencoding() pour décoder/encoder entre str et unicode

conversion between str and unicode

donc si nous changeons l'encodage par défaut, il y aura toutes sortes de problèmes incompatibles. par exemple:

# coding: utf-8
import sys

print "你好" == u"你好"
# False

reload(sys)
sys.setdefaultencoding("utf-8")

print "你好" == u"你好"
# True

Plus d'exemples:

Cela dit, je me souviens qu'il y a un blog suggérant d'utiliser unicode dans la mesure du possible, et uniquement une chaîne de bits lors des transactions avec les E/S. Je pense que si vous suivez cette convention, la vie sera beaucoup plus facile. Plus de solutions peuvent être trouvées:

1
Jiacai Liu