PEP 686: modo UTF-8 ativado por padrão no Python 3.15
(peps.python.org)- O Python 3.15 ativa o modo UTF-8 por padrão, alinhando a codificação padrão de arquivos, entrada/saída padrão e pipes para UTF-8
- UTF-8 é usado como codificação padrão de fato em arquivos-fonte, JSON·TOML·YAML, principais editores, dados da Web e linguagens como Node.js·Go·Rust·Java, aumentando a interoperabilidade
- A codificação padrão anterior variava conforme a plataforma; quando desenvolvedores Unix omitiam
encoding="utf-8", podiam surgir bugs de inconsistência no Windows e em outros ambientes - Se necessário, é possível desativar com
PYTHONUTF8=0ou-X utf8=0; para verificar compatibilidade, usam-seEncodingWarning,encoding="utf-8",encoding="locale"elocale.getencoding() - Programas que dependem da codificação padrão podem sofrer principalmente no Windows com
UnicodeError, mojibake e corrupção silenciosa de dados, exigindo verificação prévia
A codificação padrão que muda no Python 3.15
- A PEP 686 é uma mudança que ativa por padrão o modo UTF-8 da PEP 540
- Em arquivos, stdio e pipes que precisam de uma codificação padrão, o Python passa a usar UTF-8 de forma consistente
- A ativação por padrão começa no Python 3.15, e usuários podem desativá-la das seguintes formas
PYTHONUTF8=0-X utf8=0
Por que tornar UTF-8 o padrão
- UTF-8 se consolidou como a codificação de texto padrão em vários ambientes
- A codificação padrão de arquivos-fonte Python é UTF-8
- JSON, TOML e YAML usam UTF-8
- A maioria dos editores de texto, incluindo Visual Studio Code e Windows Notepad, usa UTF-8 por padrão
- A maioria dos sites e dados de texto da internet usa UTF-8
- Várias linguagens de programação populares, incluindo Node.js, Go, Rust e Java, usam UTF-8 por padrão
- Quando a codificação padrão do Python muda para UTF-8, melhora a interoperabilidade com outras ferramentas, linguagens e formatos de dados
- Muitos desenvolvedores Python em ambientes Unix esquecem que a codificação padrão depende da plataforma e omitem
encoding="utf-8"ao ler textos em UTF-8, como JSON·TOML·Markdown·arquivos-fonte Python - Diferenças de codificação padrão entre plataformas se tornam uma causa de bugs que quebram esse código em outros ambientes
Correções na API de locale e em encoding="locale"
- O modo UTF-8 afeta
locale.getpreferredencoding(False), portanto é necessária uma API para obter a codificação do locale independentemente do modo UTF-8 locale.getencoding()foi adicionada para esse propósito e retorna a codificação do locale, ignorando o modo UTF-8- Essa API foi adicionada no Python 3.11
- Quando a opção
warn_default_encodingé especificada,locale.getpreferredencoding()emite oEncodingWarningda PEP 597, assim comoopen() - A PEP 597 adicionou a opção
encoding="locale"aoTextIOWrapper, permitindo especificar explicitamente a codificação do locale - Antes, mesmo ao especificar
encoding="locale"no modo UTF-8, oTextIOWrapperusava"UTF-8"- Isso não estava alinhado com a motivação da PEP 597
- O motivo foi que não se havia previsto a situação em que o modo UTF-8 se tornaria o padrão quando a codificação de texto padrão do Python mudasse
- Essa inconsistência foi corrigida no Python 3.11; mesmo no modo UTF-8, ao passar
encoding="locale", a codificação do locale é usada
Compatibilidade retroativa e procedimento de migração
- A maioria dos sistemas Unix usa locale UTF-8, e o Python ativa o modo UTF-8 quando o locale é C ou POSIX, portanto o impacto da mudança se concentra principalmente em usuários do Windows
- Programas Python que dependem da codificação padrão podem enfrentar os seguintes problemas
UnicodeError- mojibake
- corrupção silenciosa de dados
- O procedimento recomendado para corrigir problemas de compatibilidade retroativa é o seguinte
- Desativar o modo UTF-8
- Usar o
EncodingWarningda PEP 597 para encontrar pontos afetados pelo modo UTF-8- Quando a opção
encodingfor omitida, considerar usarencoding="utf-8"ouencoding="locale" - Quando
locale.getpreferredencoding()for usado, considerar usar"utf-8"oulocale.getencoding()
- Quando a opção
- Testar a aplicação no modo UTF-8
Casos anteriores em Ruby·Java e alternativas rejeitadas
- O Ruby alterou o
external_encodingpadrão no Windows para UTF-8 no Ruby 3.0, em 2020 - O Java alterou a codificação de texto padrão para UTF-8 no JDK 18, em 2022
- Tanto Ruby quanto Java oferecem opções para compatibilidade retroativa, mas não fornecem um alerta de uso da codificação padrão como o
EncodingWarningdo Python - A proposta de abandonar o próprio uso da codificação padrão foi rejeitada
- Há muitos casos em que a codificação padrão é usada apenas para ler e escrever texto ASCII
- Em aplicações não multiplataforma que rodam apenas em Unix, esse tipo de alerta não é útil
- Obrigar
encodingem todos os lugares impõe uma carga grande aos usuários, e muitosDeprecationWarningpodem fazer com que os usuários ignorem os avisos - A PEP 387 exige a adição de avisos para mudanças que quebram compatibilidade retroativa, mas não exige necessariamente
DeprecationWarning
- A proposta de usar
PYTHONIOENCODINGcomo codificação padrão dos pipes do módulosubprocesstambém foi rejeitada- Essa abordagem permitiria usar uma codificação legada em
subprocess.Popen(text=True)mesmo no modo UTF-8 - Mas ela tornaria a “codificação padrão” mais complexa, e a própria abordagem também é uma mudança que quebra compatibilidade retroativa
- Usuários podem desativar o modo UTF-8 até trocar
text=Trueporencoding="utf-8"ouencoding="locale"
- Essa abordagem permitiria usar uma codificação legada em
Do ponto de vista da educação dos usuários
- Novos usuários terão menos necessidade de aprender sobre codificação de texto durante o primeiro ano
- Eles só precisarão aprender sobre codificação quando tiverem de lidar com arquivos de texto que não sejam UTF-8
- Usuários existentes devem verificar os pontos afetados seguindo o procedimento de compatibilidade retroativa
1 comentários
Opiniões no Hacker News
Sempre foi irritante que a codificação padrão de arquivos de texto variasse conforme a plataforma, então essa mudança é bem-vinda.
Também é bom que não tenham tentado mexer na codificação do sistema de arquivos. Isso é outro problema, e bem espinhoso por si só.
Foi um grande erro o Windows ter passado tanto tempo sem oferecer uma forma simples de escolher que funções ANSI como
TextOutAusassem a página de código UTF-8. Isso se tornou possível via arquivo de manifest mais ou menos no meio do desenvolvimento do Windows 10, mas esse tipo de recurso deveria ter entrado na época do NT4 ou do Windows 98.Dependia não só da plataforma, mas também da localidade preferida do usuário, e a biblioteca padrão de C se comporta do mesmo jeito. Por exemplo, em Unix/Linux,
iso-8859-1era comum para línguas da Europa Ocidental e, depois da introdução do euro, tornou-se frequente mudar paraiso-8859-15, que tinha o símbolo€. O UTF-8 começou a funcionar sem grandes problemas lá pelo fim dos anos 2000, e o Debian mudou o padrão para UTF-8 no lançamento Etch.No notebook da empresa, todos os testes locais funcionavam bem, mas, ao implantar em um host Linux, uma aplicação subordinada exigia CRLF e não conseguiu consumir o arquivo. É um daqueles probleminhas bobos que às vezes precisamos lembrar. Dito isso, também é uma pergunta válida por que um software recém-escrito exigiria um terminador de linha específico.
Não depender de padrões instáveis do sistema é uma coisa boa.
Esses valores tendem, em algum momento, a voltar diferentes do que eu presumia. Alguns anos atrás, ao lidar com Ubuntu e scripts
init.d, um script que iniciava Java estava sendo executado como root — e isso era antes do Docker, então era ainda mais comum — em um shell que não configurava o padrão UTF-8 normal para usuários comuns. O resultado foi expor um uso ruim de APIs do Java que usavam o padrão do SO.Hoje em dia, a maioria das APIs tem variantes em que é possível especificar a codificação, e analisadores estáticos de código avisam quando você usa a errada. Mas basta faltar em um único ponto para o conteúdo começar a quebrar. Agora é muito provável que o uso de codificações que não sejam UTF-8 seja, na maioria das vezes, não intencional; se for intencional, isso deve ser explicitado, em vez de depender de alguma configuração indireta estranha do SO. Portanto, é uma boa mudança, e é melhor que o código que quebrar por causa disso receba uma correção simples.
.gitignorecriado por uma funçãotouchfeita como alias no PowerShell, e o Git simplesmente não o respeitava de jeito nenhum.Ao verificar, descobri que o arquivo de texto gerado era UTF-16, então estava sendo basicamente ignorado. Aprendi a lição e mudei o padrão do sistema para UTF-8, mas hoje simplesmente dependo do editor de texto.
Se
printf("%f", 4.2)imprime magicamente strings diferentes dependendo do ambiente, isso cria mais problemas do que resolve. Quando se deseja comportamento dependente de localidade, as informações de localidade ou as partes relevantes devem ser passadas explicitamente para a função.Há uma heurística que vem ficando cada vez mais correta nas últimas décadas: se existe uma configuração de
charsetem algum lugar e ela não é UTF-8, está errada.Python 2 era indiferente ao conjunto de caracteres e, por isso, sempre funcionava, mas a melhoria do Python 3 não foi simplesmente uma melhoria. A forma de distinguir um script Python 3 de um script Python 2 é esta: se contém a string
utf-8, é Python 3; se só funciona na localidadeC.UTF-8, é Python 3. Entendo essa mudança como um “conserto” do Python 3, então a recebo bem.Achei que isso já fosse o padrão desde o Python 3.
u""deixou de ser necessário no Python 3.Acabei de digitar
"éķů"no Python 2.7 e ele imprimiu os bytes UTF-8 desses caracteres, então não tenho muita certeza do que exatamente o prefixoufazia, mas uma das grandes mudanças na passagem do Python 2 para o 3 foi que strings passaram a ter codificação, enquanto strings de bytes viraram sequências de bytes sem codificação. Esta mudança parece tratar principalmente do problema de, em ambientes cuja codificação padrão não é UTF-8, como o Windows, ser necessário especificaropen('filename', mode='r', encoding='UTF-8')ao usaropen('filename', mode='r').Como em
Path("filenames use their own encoding").write_text("file content encoding uses yet another encoding"), a codificação de literais de string, nomes de arquivo e conteúdo de arquivos é diferente em cada caso. As codificações correspondentes são o UTF-8 detokenize.open,sys.getfilesystemencoding()deos.fsencodeelocale.getpreferredencoding()deopen.A frase “outras linguagens de programação populares, incluindo Node.js, Go, Rust e Java, também usam UTF-8 por padrão” — eu perdi quando o Java migrou de UTF-16 para UTF-8.
Internamente, a classe
Stringainda usa as codificações UTF-16 e latin-1, e a JVM usa, como antes, uma codificação UTF-8 modificada. A classeStringoriginalmente usava apenas UTF-16, mas desde o Java 9 também usa a codificação latin-1 de 1 byte por caractere quando possível.O Java nunca usou UTF-16 como padrão para codificação de leitura/escrita.
A codificação interna do CPython agora é UTF-8?
Strings em Python podem ser indexadas por subscrito, mas acesso aleatório é raro o bastante para que pareça aceitável fazer uma indexação preguiçosa quando necessário. Se você só precisa avançar ou retroceder uma posição, não precisa de índice, então também é perfeitamente possível usar UTF-8 como representação interna
stré um objetoPyUnicodeQuando bytes UTF-8 são solicitados, um objeto
bytesé criado conforme necessário e armazenado em cache como parte doPyUnicode, sendo liberado junto quando oPyUnicodeé liberado. Separadamente, os code points que compõem a string são armazenados em um array simples para permitir acesso aleatório. O tamanho de cada code point pode ser de 1, 2 ou 4 bytes; ao criar umPyUnicode, se você especifica o valor máximo de code point, ele é arredondado para um de 127, 255, 65535 ou 1.114.111, determinando se serão usados 1/2/4 bytesSe o valor máximo de code point for 127, essa representação em array pode ser usada diretamente como UTF-8. Então a resposta à pergunta é que muitas strings em que todos os code points são 127 ou menos são armazenadas como UTF-8. Porém, ao percorrer uma string, não se deve fazer isso por code point. Um caractere percebido pelo usuário, isto é, um cluster de grafemas, é composto por um ou mais code points. Por exemplo, um e com acento pode ser o code point
eseguido de um code point de acento combinante, e o emoji de fênix é composto pelo emoji de pássaro, um conector de largura zero e o emoji de fogo. Alguns sistemas de escrita usados por centenas de milhões de pessoas também funcionam de modo parecido, com sinais combinantes que indicam vogais anexados a consoantes. Este- -tem 5 code points, e há um bom texto sobre como várias linguagens reportam seu “comprimento”: https://hsivonen.fi/string-length/. Isso vem da experiência de ter acabado de implementar, como extensão C para Python, a Unicode TR29 que trata dessa parteFico me perguntando por que não
utf-8-sig. Ele lida com o BOM opcional, e ainda na semana passada tive que corrigir um script por causa dissoIsso nem é recomendado, e hoje considero razoável também o comportamento de falhar ao encontrar um BOM
Já que o assunto é UTF-8, o framebuffer do Linux já deveria ter ganhado suporte UTF-8 de verdade há muito tempo
Quero dizer suporte real, não de 256/512 glifos. Até o GNU Hurd já tinha, por volta de 2007, um console de terminal melhor com suporte a UTF-8, e agora estamos em 2024
Ótimo. Agora só falta o JS mudar para UTF-8
Claro que o JS não pode ser melhorado. Porque, ao contrário de qualquer outra linguagem de programação, ele precisa ser compatível com código escrito em 1995
A representação interna das strings é uma questão separada e, assim como JavaScript, Python também não usa “simplesmente UTF-8” internamente
Em “muitos desenvolvedores Python que usam Unix esquecem que a codificação padrão depende da plataforma e omitem
encoding="utf-8"ao ler arquivos de texto codificados em UTF-8”, talvez não seja tanto que tenham esquecido, mas sim que isso não seja suficientemente conhecidoSinceramente, eu achava que o Python usava apenas UTF-8 em todos os lugares, a menos que você pedisse explicitamente algo diferente
bytes.decodeestr.encodeusam UTF-8 como padrão pelo menos desde o Python 3. Já a codificação padrão ao decodificar nomes de arquivo usasys.getfilesystemencoding(); no Windows e no macOS isso também é UTF-8, mas no Linux depende do locale, mais especificamente deCODESET. Por fim,openusa diretamentelocale.getencoding()