- Python 3.15 active par défaut le mode UTF-8, alignant l’encodage par défaut des fichiers, des entrées/sorties standard et des pipes sur UTF-8
- UTF-8 est utilisé comme encodage de facto standard pour les fichiers source, JSON·TOML·YAML, les principaux éditeurs, les données web, ainsi que Node.js·Go·Rust·Java, ce qui améliore l’interopérabilité
- L’encodage par défaut actuel varie selon la plateforme ; si des développeurs Unix omettent
encoding="utf-8", cela peut provoquer des bugs d’incohérence sur Windows et ailleurs - Si nécessaire, il est possible de le désactiver avec
PYTHONUTF8=0ou-X utf8=0; pour vérifier la compatibilité, on utiliseEncodingWarning,encoding="utf-8",encoding="locale"etlocale.getencoding() - Les programmes qui dépendent de l’encodage par défaut peuvent subir, principalement sous Windows, des
UnicodeError, du mojibake ou une corruption silencieuse des données ; une vérification préalable est donc nécessaire
Ce qui change dans l’encodage par défaut avec Python 3.15
- PEP 686 est un changement qui active par défaut le mode UTF-8 de PEP 540
- Pour les fichiers, stdio et pipes qui nécessitent un encodage par défaut, Python utilise systématiquement UTF-8
- Il est activé par défaut à partir de Python 3.15, et les utilisateurs peuvent le désactiver de la manière suivante
PYTHONUTF8=0-X utf8=0
Pourquoi choisir UTF-8 comme valeur par défaut
- UTF-8 s’est imposé comme l’encodage de texte standard dans de nombreux environnements
- L’encodage par défaut des fichiers source Python est UTF-8
- JSON, TOML et YAML utilisent UTF-8
- La plupart des éditeurs de texte, dont Visual Studio Code et Windows Notepad, utilisent UTF-8 par défaut
- La majorité des sites web et des données textuelles sur Internet utilisent UTF-8
- Plusieurs langages de programmation populaires, dont Node.js, Go, Rust et Java, utilisent UTF-8 par défaut
- Le passage de l’encodage par défaut de Python à UTF-8 améliore l’interopérabilité avec les autres outils, langages et formats de données
- Beaucoup de développeurs Python en environnement Unix oublient que l’encodage par défaut dépend de la plateforme, et omettent
encoding="utf-8"lorsqu’ils lisent du texte UTF-8, par exemple des fichiers JSON, TOML, Markdown ou des fichiers source Python - Les différences d’encodage par défaut entre plateformes deviennent alors une cause de bugs, ce code pouvant échouer dans d’autres environnements
Correction de l’API locale et de encoding="locale"
- Le mode UTF-8 affecte
locale.getpreferredencoding(False); il faut donc une API permettant d’obtenir l’encodage de la locale indépendamment du mode UTF-8 locale.getencoding()a été ajouté à cette fin : il renvoie l’encodage de la locale tout en ignorant le mode UTF-8- Cette API a été ajoutée dans Python 3.11
- Lorsque l’option
warn_default_encodingest spécifiée,locale.getpreferredencoding()émet unEncodingWarningde PEP 597, commeopen() - PEP 597 a ajouté l’option
encoding="locale"àTextIOWrapper, afin de pouvoir spécifier explicitement l’encodage de la locale - Auparavant, même si
encoding="locale"était spécifié en mode UTF-8,TextIOWrapperutilisait"UTF-8"- Cela ne correspondait pas à la motivation de PEP 597
- La raison est que la situation où le mode UTF-8 deviendrait la valeur par défaut lors du changement de l’encodage texte par défaut de Python n’avait pas été anticipée
- Cette incohérence a été corrigée dans Python 3.11 : même en mode UTF-8, passer
encoding="locale"utilise l’encodage de la locale
Rétrocompatibilité et procédure de migration
- La plupart des systèmes Unix utilisent une locale UTF-8, et Python active le mode UTF-8 lorsque la locale est C ou POSIX ; l’impact du changement se concentre donc principalement sur les utilisateurs Windows
- Les programmes Python qui dépendent de l’encodage par défaut peuvent rencontrer les problèmes suivants
UnicodeError- mojibake
- corruption silencieuse des données
- La procédure recommandée pour corriger les problèmes de rétrocompatibilité est la suivante
- Désactiver le mode UTF-8
- Repérer avec l’
EncodingWarningde PEP 597 les endroits affectés par le mode UTF-8- Lorsque l’option
encodingest omise, envisager d’utiliserencoding="utf-8"ouencoding="locale" - Lorsque
locale.getpreferredencoding()est utilisé, envisager d’utiliser"utf-8"oulocale.getencoding()
- Lorsque l’option
- Tester l’application en mode UTF-8
Précédents de Ruby·Java et alternatives rejetées
- Ruby a changé l’
external_encodingpar défaut de Windows en UTF-8 avec Ruby 3.0, en 2020 - Java a changé l’encodage texte par défaut en UTF-8 avec JDK 18, en 2022
- Ruby et Java fournissent tous deux des options de rétrocompatibilité, mais pas d’avertissement sur l’utilisation de l’encodage par défaut comme l’
EncodingWarningde Python - L’abandon pur et simple de l’utilisation de l’encodage par défaut a été rejeté
- L’encodage par défaut est souvent utilisé pour lire et écrire uniquement du texte ASCII
- Pour les applications non multiplateformes qui ne s’exécutent que sous Unix, ce type d’avertissement n’est pas utile
- Imposer
encodingpartout ferait peser une charge importante sur les utilisateurs, et de nombreuxDeprecationWarningpourraient les amener à ignorer les avertissements - PEP 387 exige l’ajout d’un avertissement pour les changements qui cassent la rétrocompatibilité, mais n’exige pas nécessairement un
DeprecationWarning
- L’option consistant à utiliser
PYTHONIOENCODINGcomme encodage par défaut des pipes du modulesubprocessa également été rejetée- Cette approche permettrait à
subprocess.Popen(text=True)d’utiliser un encodage hérité même en mode UTF-8 - Mais elle complexifierait l’« encodage par défaut », et cette approche constituerait elle-même un changement cassant la rétrocompatibilité
- Les utilisateurs peuvent désactiver le mode UTF-8 jusqu’à ce qu’ils remplacent
text=Trueparencoding="utf-8"ouencoding="locale"
- Cette approche permettrait à
Point de vue de l’éducation des utilisateurs
- Les nouveaux utilisateurs auront moins besoin d’apprendre les encodages de texte pendant leur première année
- Ils pourront apprendre les encodages lorsqu’ils devront manipuler des fichiers texte non UTF-8
- Les utilisateurs existants doivent vérifier les points affectés en suivant la procédure de rétrocompatibilité
1 commentaires
Avis sur Hacker News
Le fait que l’encodage par défaut des fichiers texte varie selon la plateforme a toujours été pénible, donc ce changement est bienvenu
C’est aussi une bonne chose qu’on n’essaie pas de toucher à l’encodage du système de fichiers. C’est un problème distinct, et lui aussi source de maux de tête
Le fait que Windows n’ait longtemps pas fourni de moyen simple pour faire utiliser la page de code UTF-8 aux fonctions ANSI comme
TextOutAa été une grosse erreur. Le support via fichier manifest n’est arrivé qu’au milieu du développement de Windows 10, alors qu’une telle fonctionnalité aurait dû exister dès l’époque de NT4 ou Windows 98Cela dépendait non seulement de la plateforme, mais aussi de la locale préférée de l’utilisateur, et la bibliothèque standard C fonctionne de la même façon. Par exemple, sur Unix/Linux,
iso-8859-1était courant pour les langues d’Europe occidentale, puis avec l’arrivée de l’euro, beaucoup sont passés àiso-8859-15, qui inclut le symbole€. L’UTF-8 n’a commencé à fonctionner sans friction que vers la fin des années 2000, et Debian a adopté UTF-8 par défaut avec la version EtchTous les tests locaux sur le portable de l’entreprise passaient, mais une fois déployé sur un hôte Linux, une sous-application ne pouvait rien traiter parce qu’elle exigeait du CRLF. C’est l’un de ces petits problèmes idiots qu’il faut parfois se rappeler. Cela dit, on peut légitimement se demander pourquoi un logiciel écrit aujourd’hui exige encore un terminateur de ligne particulier
Ne pas dépendre de valeurs système par défaut instables est une bonne chose
Ces valeurs ont tendance à se comporter différemment de ce qu’on imagine à un moment ou à un autre. Il y a quelques années, en travaillant avec Ubuntu et des scripts
init.d, un script qui lançait Java s’exécutait en root, ce qui était encore plus fréquent avant Docker, dans un shell qui ne configurait pas de valeur UTF-8 correcte par défaut pour les utilisateurs normaux. Cela a mis en évidence l’utilisation d’une mauvaise API Java qui s’appuyait sur la valeur par défaut de l’OSAujourd’hui, la plupart des API ont des variantes qui permettent d’indiquer explicitement l’encodage, et les analyseurs statiques avertissent aussi quand on utilise la mauvaise. Mais il suffit d’un seul oubli pour que le contenu commence à être corrompu. Désormais, l’utilisation d’un encodage autre que l’UTF-8 est très probablement involontaire dans la plupart des cas, et si elle est voulue, il faut l’indiquer explicitement au lieu de s’appuyer sur une configuration implicite et bizarre de l’OS. Donc c’est un bon changement, et mieux vaut que le code cassé ici reçoive un correctif simple
.gitignorecréé par une fonctiontouchdéfinie comme alias dans PowerShell, mais Git refusait obstinément de le respecterAprès vérification, le fichier texte créé était en UTF-16 et était donc en pratique ignoré. J’en ai tiré la leçon et j’ai changé la valeur système par défaut pour UTF-8, mais aujourd’hui je m’en remets simplement à un éditeur de texte
Si
printf("%f", 4.2)produit comme par magie une chaîne différente selon l’environnement, cela crée plus de problèmes que cela n’en résout. Quand on veut un comportement dépendant de la locale, il faut passer explicitement à la fonction les informations de locale, ou du moins la partie pertinenteDepuis quelques décennies, une heuristique s’est progressivement imposée : s’il y a un réglage
charsetquelque part, alors s’il n’est pas en UTF-8, c’est fauxPython 2 était indifférent au jeu de caractères et fonctionnait donc toujours, mais les améliorations de Python 3 n’étaient pas uniquement des améliorations simples. Voici comment distinguer un script Python 3 d’un script Python 2 : s’il contient la chaîne
utf-8, c’est du Python 3, et s’il ne fonctionne que dans une localeC.UTF-8, c’est du Python 3. Ce changement est bienvenu parce qu’il donne l’impression de “réparer” Python 3Je pensais que c’était déjà la valeur par défaut depuis Python 3
u""est devenu inutile en Python 3Je viens d’essayer de saisir
"éķů"dans Python 2.7, et il affiche les octets UTF-8 correspondants, donc je ne suis pas certain de ce que faisait exactement le préfixeu, mais l’un des grands changements entre Python 2 et 3 est que les chaînes ont un encodage, tandis que les chaînes d’octets sont devenues de simples séquences d’octets sans encodage. Ce changement semble surtout concerner le fait que, dans des environnements où l’encodage par défaut n’est pas UTF-8, comme Windows, il fallait indiquer explicitementopen('filename', mode='r', encoding='UTF-8')au lieu de simplement utiliseropen('filename', mode='r')Comme dans
Path("filenames use their own encoding").write_text("file content encoding uses yet another encoding"), les littéraux de chaînes, les noms de fichiers et le contenu des fichiers relèvent chacun d’encodages différents. Les encodages correspondants sont l’UTF-8 detokenize.open, lesys.getfilesystemencoding()deos.fsencode, et lelocale.getpreferredencoding()deopen« D’autres langages de programmation populaires, dont Node.js, Go, Rust et Java, utilisent eux aussi l’UTF-8 par défaut » : j’ai raté le moment où Java est passé de l’UTF-16 à l’UTF-8
À l’intérieur de la classe
String, les encodages UTF-16 et latin-1 sont toujours utilisés, et la JVM continue d’utiliser comme auparavant un encodage UTF-8 modifié. À l’origine, la classeStringutilisait uniquement UTF-16, mais depuis Java 9, elle utilise aussi un encodage latin-1 sur 1 octet par caractère quand c’est possibleJava n’a jamais utilisé UTF-16 comme valeur par défaut pour l’encodage de lecture/écriture
L’encodage interne de CPython est-il désormais UTF-8 ?
Les chaînes Python peuvent être indexées par position, mais l’accès aléatoire est suffisamment rare pour qu’une indexation paresseuse au besoin semble acceptable. S’il suffit d’avancer ou de reculer d’un caractère, pas besoin d’index, donc une représentation interne en UTF-8 paraît tout à fait possible
PyUnicodequi représente unstrQuand les octets UTF-8 sont demandés, un objet
bytesest créé si nécessaire, mis en cache comme partie dePyUnicode, puis libéré en même temps quePyUnicode. Séparément, les points de code qui composent la chaîne sont stockés dans un tableau simple afin de permettre l’accès aléatoire. Chaque point de code peut occuper 1, 2 ou 4 octets, et lors de la création duPyUnicode, la valeur maximale de point de code indiquée est arrondie à l’une des valeurs 127, 255, 65535 ou 1 114 111, ce qui détermine l’usage de 1/2/4 octets.Si la valeur maximale de point de code est 127, cette représentation en tableau peut être utilisée directement comme UTF-8. Donc la réponse à la question est que beaucoup de chaînes dont tous les points de code sont inférieurs ou égaux à 127 sont bien stockées en UTF-8. En revanche, il ne faut pas parcourir une chaîne au niveau des points de code. Les caractères perçus par l’utilisateur, c’est-à-dire les grapheme clusters, sont composés d’un ou plusieurs points de code. Par exemple, un e accentué peut être composé du point de code
esuivi d’un point de code d’accent combinant, et l’emoji phénix est constitué de l’emoji oiseau, d’un liant sans chasse et de l’emoji feu. Certains systèmes d’écriture utilisés par des centaines de millions de personnes fonctionnent aussi de manière similaire, avec des signes combinants indiquant les voyelles attachés aux consonnes. Ce- -contient 5 points de code, et il existe un bon article sur la façon dont plusieurs langages rapportent cette « longueur » : https://hsivonen.fi/string-length/. Cela vient de mon expérience récente à implémenter Unicode TR29 en extension C pour PythonJe me demande pourquoi pas
utf-8-sig. Ça gère le BOM optionnel, et j’ai encore dû corriger un script à cause de ça la semaine dernièreCe n’est pas recommandé, et de nos jours je considère même comme raisonnable un comportement qui échoue à cause d’un BOM
Puisqu’on parle d’UTF-8, le framebuffer Linux aurait dû avoir un vrai support UTF-8 depuis longtemps
Un vrai support, pas les consoles limitées à 256/512 glyphes. Même GNU Hurd avait déjà une meilleure console terminal avec support UTF-8 vers 2007, et on est en 2024
Bien. Maintenant, il ne reste plus qu’à faire passer JS à l’UTF-8
Bien sûr, JS ne peut pas être amélioré. Contrairement à tout autre langage de programmation, il doit rester compatible avec du code écrit en 1995
La représentation interne des chaînes est une question distincte, et comme JavaScript, Python n’utilise pas simplement « de l’UTF-8 » en interne
À propos de « de nombreux développeurs Python sur Unix oublient que l’encodage par défaut dépend de la plateforme et omettent
encoding="utf-8"lorsqu’ils lisent des fichiers texte encodés en UTF-8 », ce n’est peut-être pas tant un oubli qu’un fait insuffisamment connuHonnêtement, je pensais que Python utilisait uniquement UTF-8 partout, sauf demande explicite du contraire
bytes.decodeetstr.encodeutilisent par défaut UTF-8 au moins depuis Python 3. En revanche, l’encodage par défaut pour décoder les noms de fichiers utilisesys.getfilesystemencoding(), qui vaut aussi UTF-8 sous Windows et macOS, mais dépend de la locale — plus précisément deCODESET— sous Linux. Enfin,openutilise directementlocale.getencoding()