[[ch_str_bytes]]
== Unicode versus Bytes
:example-number: 0
:figure-number: 0

[quote, Esther Nam e Travis Fischer]
____
Humanos usam texto.
Computadores falam em bytes.footnote:[Slide 12 da palestra "Character Encoding and Unicode in Python"
(_Codificação de Caracteres e Unicode no Python_) na PyCon 2014 («slides» [.small]#&#91;fpy.li/4-1&#93;# , «vídeo» [.small]#&#91;fpy.li/4-2&#93;# ).]
____


O Python 3 introduziu uma forte distinção entre strings de texto humano e sequências de bytes em estado bruto.
A conversão automática((("implicit conversion"))) de sequências de bytes para
texto Unicode ficou para trás no Python 2.
Este capítulo trata de strings Unicode, sequências de bytes,
e das codificações usadas para converter umas nas outras.

Dependendo do que você faz com Python, pode achar que entender o Unicode não é importante.
Isso é improvável, mas mesmo que seja o caso, não há como escapar da separação entre `str` e `bytes`,
que agora exige conversões explícitas.
Como um bônus, você descobrirá que os tipos especializados de sequências binárias `bytes` e `bytearray`
oferecem recursos que a classe `str` "pau para toda obra" de Python 2 não oferecia.

Nesse((("Unicode text versus bytes", "topics covered"))) capítulo, veremos os seguintes tópicos:

* Caracteres, pontos de código e representações binárias
* Recursos exclusivos das sequências binárias: `bytes`, `bytearray`, e `memoryview`
* Codificando para o Unicode completo e para conjuntos de caracteres legados
* Evitando e tratando erros de codificação
* Melhores práticas para lidar com arquivos de texto
* A armadilha da codificação default e questões de E/S padrão
* Comparações seguras de texto Unicode com normalização
* Funções utilitárias para normalização, _case folding_ (equiparação maiúsculas/minúsculas)
e remoção de sinais diacríticos por força bruta
* Ordenação correta de texto Unicode com `locale` e a biblioteca _pyuca_
* Metadados de caracteres do banco de dados Unicode
* APIs duais, que processam `str` e `bytes`


=== Novidades neste capítulo

O suporte((("Unicode text versus bytes", "significant changes to"))) ao Unicode no Python 3 sempre foi muito completo e estável,
então o acréscimo mais notável é a <<finding_chars_sec>>,
descrevendo um utilitário de linha de comando para busca no banco de dados Unicode—uma forma de encontrar
gatinhos sorridentes ou hieroglifos do Egito antigo.

Vale a pena mencionar que o suporte a Unicode no Windows ficou melhor e mais simples desde o Python 3.6,
como veremos na <<encoding_defaults>>.

Vamos começar então com os conceitos não-tão-novos mas fundamentais de caracteres, pontos de código e bytes.

[NOTE]
====
Para((("struct module")))((("binary records, parsing with struct"))) essa segunda edição,
expandi a seção sobre o módulo `struct` e o publiquei online em
«_Parsing binary records with struct_ (Analisando registros binários com struct)» [.small]#&#91;fpy.li/4-3&#93;#, 
no
http://fluentpython.com[fluentpython.com] o website que complementa o livro em inglês.

Lá((("emojis", "building"))) você também vai encontrar o
«_Building Multi-character Emojis_ (Criando emojis multi-caractere)» [.small]#&#91;fpy.li/4-4&#93;# ,
descrevendo como combinar caracteres Unicode para criar bandeiras de países, bandeiras de arco-íris,
pessoas com tonalidades de pele diferentes e ícones de diferentes tipos de famílias.
====


=== Questões de caracteres

O((("Unicode text versus bytes", "characters and Unicode standard", id="UTVchar04")))
conceito de "string" é simples: uma string é uma sequência de caracteres.
O problema está na definição de "caractere".

Em 2023, a melhor definição de "caractere" que temos é um caractere Unicode.
Consequentemente, os itens que compõe um `str` de Python 3 são caracteres Unicode,
como os itens de um objeto `unicode` no Python 2.
Em contraste, os itens de uma `str` no Python 2 são bytes, assim como os itens num objeto `bytes` de Python 3.

<<<
O padrão Unicode separa explicitamente a identidade dos caracteres de representações binárias específicas:

* A identidade de um caractere é chamada de ((("code points")))ponto de código (__code point__).
É um número de 0 a 1.114.111 (na base 10),
formatado no padrão Unicode como 4 a 6 dígitos hexadecimais precedidos pelo prefixo "U+", de U+0000 a U+10FFFF.
Por exemplo, o ponto de código da letra A é U+0041, o símbolo do Euro é U+20AC,
e o símbolo musical da clave de sol corresponde ao ponto de código U+1D11E.
Cerca de 13% dos pontos de código possíveis têm caracteres atribuídos no Unicode 13,
a versão do padrão usada no Python 3.10.
* Os bytes específicos que representam um caractere dependem da((("encoding", "definition of")))
codificação (_encoding_) usada.
Uma codificação, nesse contexto, é um algoritmo que converte pontos de código para sequências de bytes, e vice-versa.
O ponto de código para a letra A (U+0041) é codificado como um único byte, `\x41`, na codificação UTF-8,
ou como os bytes `\x41\x00` na codificação UTF-16LE.
Em um outro exemplo, o UTF-8 exige três bytes para codificar o símbolo do Euro (U+20AC): `\xe2\x82\xac`.
Mas no UTF-16LE o mesmo ponto de código é representado em dois bytes: `\xac\x20`.

Converter pontos de código para bytes é _codificar_;
converter bytes para pontos de código é((("decoding", "definition of"))) _decodificar_.
Veja o <<ex_encode_decode>>.

[[ex_encode_decode]]
.Codificando e decodificando
====
[source, python]
----
>>> s = 'café'
>>> len(s)  # <1>
4
>>> b = s.encode('utf8')  # <2>
>>> b
b'caf\xc3\xa9'  # <3>
>>> len(b)  # <4>
5
>>> b.decode('utf8')  # <5>
'café'
----
====
<1> A `str` `'café'` tem quatro caracteres Unicode.
<2> Codifica `str` para `bytes` usando a codificação UTF-8.
<3> `bytes` literais são prefixados com um `b`.
<4> `bytes` `b` tem cinco bytes (o ponto de código para "é" é codificado com dois bytes em UTF-8).
<5> Decodifica `bytes` para `str` usando a codificação UTF-8.

[TIP]
====
Um jeito fácil de memorizar a distinção entre `.decode()` e `.encode()` é se convencer
que sequências de bytes podem ser enigmáticos dumps de código de máquina,
ao passo que objetos `str` Unicode são texto "humano".
Daí que faz sentido _decodificar_ `bytes` em `str`, para obter texto legível por seres humanos,
e _codificar_ `str` em `bytes`, para armazenamento ou transmissão.
====

Apesar do `str` de Python 3 ser praticamente igual ao tipo `unicode` de Python 2 com um novo nome,
o `bytes` de Python 3 não é meramente o velho `str` renomeado,
e há também o tipo estreitamente relacionado `bytearray`.
Então vale a pena examinar os tipos de sequências binárias antes de avançar para
questões de codificação/decodificação.((("", startref="UTVchar04")))


=== Os fundamentos do byte

Os((("Unicode text versus bytes", "byte essentials", id="UTVbytes04")))
novos tipos de sequências binárias são diferentes do `str` de Python 2 em vários aspectos.
A primeira coisa importante é que existem dois tipos embutidos básicos de((("binary sequences"))) sequências binárias:
o tipo imutável `bytes`, introduzido no Python 3, e o tipo mutável `bytearray`,
introduzido há tempos, no Python 2.6footnote:[Python 2.6 e o 2.7 também tinham um `bytes`,
mas ele era só um apelido (_alias_) para o tipo `str`.].
A documentação de Python algumas vezes usa o termo genérico "byte string"
(_string de bytes_, na documentação em português) para se referir a `bytes` e `bytearray`.

Cada item em `bytes` ou `bytearray` é um inteiro entre 0 e 255,
e não uma string de um caractere, como no `str` de Python 2.
Entretanto, uma fatia de uma sequência binária sempre produz
uma sequência binária do mesmo tipo—incluindo fatias de tamanho 1. Veja o <<ex_bytes_bytearray>>.

[[ex_bytes_bytearray]]
.Uma sequência de cinco bytes, como `bytes` e como `bytearray`
====
[source, python]
----
>>> cafe = bytes('café', encoding='utf_8')  <1>
>>> cafe
b'caf\xc3\xa9'
>>> cafe[0]  <2>
99
>>> cafe[:1]  <3>
b'c'
>>> cafe_arr = bytearray(cafe)
>>> cafe_arr  <4>
bytearray(b'caf\xc3\xa9')
>>> cafe_arr[-1:]  <5>
bytearray(b'\xa9')
----
====
<1> `bytes` pode ser criado a partir de uma `str`, dada uma codificação.
<2> Cada item é um inteiro em `range(256)`.
<3> Fatias de `bytes` também são `bytes`&#x2014;mesmo fatias de um único byte.
<4> A sintaxe literal para `bytearray` é `bytearray(…)` com um literal `bytes` como argumento.
<5> Uma fatia de `bytearray` também é um `bytearray`.

[WARNING]
====
O fato de `my_bytes[0]` obter um `int` mas `my_bytes[:1]` devolver uma sequência de `bytes` de tamanho 1
só é surpreendente porque estamos acostumados com o tipo `str` de Python, onde `s[0] == s[:1]`.
Para todos os outros tipos de sequência no Python, um item não é o mesmo que uma fatia de tamanho 1.
====

Apesar de sequências binárias serem, na verdade, sequências de inteiros,
sua notação literal reflete o fato delas frequentemente embutirem texto ASCII.
Assim, quatro formas diferentes de apresentação são utilizadas, dependendo do valor de cada byte:

* Para bytes com código decimais de 32 a 126&#x2014;do espaço ao `~` (til)—é usado o próprio caractere ASCII.
* Para os bytes correspondendo a tab, quebra de linha, carriage return (CR) e `\`,
são usadas as sequências de escape `\t`, `\n`, `\r`, e `\\`.
* Se os dois delimitadores de string, `'` e `"`, aparecem na sequência de bytes,
a sequência inteira é delimitada com `'`, e qualquer `'` dentro da sequência é precedida do caractere de escape,
assim `\'`.footnote:[Trívia: O caractere ASCII "aspas simples",
que o Python usa como delimitador de strings,
na verdade, se chama APOSTROPHE no padrão Unicode.
As aspas simples reais são assimétricas: a da esquerda é U+2018 e a da direita U+2019.]
* Para qualquer outro valor do byte, é usada uma sequência de escape hexadecimal (por exemplo, `\x00` é o byte nulo).

É por isso que no <<ex_bytes_bytearray>> vemos `b&#39;caf\xc3\xa9'`:
os primeiros três bytes, `b&#39;caf'`, estão na faixa de impressão do ASCII, ao contrário dos dois últimos.

Tanto `bytes` quanto `bytearray` suportam todos os métodos de `str`, exceto aqueles relacionados a formatação (`format`, `format_map`)
e aqueles que dependem de dados Unicode, incluindo `casefold`, `isdecimal`, `isidentifier`, `isnumeric`, `isprintable`, e `encode`.
Isso significa que você pode usar os métodos conhecidos de string,
como `endswith`, `replace`, `strip`, `translate`, `upper` e dezenas de outros,
com sequências binárias—mas com argumentos `bytes` em vez de `str`.
Além disso, as funções de expressões regulares no módulo `re` também funcionam com sequências binárias,
se a regex for compilada a partir de uma sequência binária ao invés de uma `str`.
Desde o Python 3.5, o operador `%` voltou a funcionar com sequências binárias.footnote:[Ele não funcionava de Python 3.0 ao 3.4,
causando muitas dores de cabeça nos desenvolvedores que lidam com dados binários.
A decisão está documentada na
«_PEP 461–Adding % formatting to bytes and bytearray_ (Acrescentando formatação com % a bytes e bytearray)» [.small]#&#91;fpy.li/pep461&#93;#. ]

As sequências binárias têm um método de classe que `str` não possui, chamado `fromhex`,
que cria uma sequência binária a partir da análise de pares de dígitos hexadecimais,
separados opcionalmente por espaços:

[source, python]
----
>>> bytes.fromhex('31 4B CE A9')
b'1K\xce\xa9'
----

As outras formas de criar instâncias de `bytes` ou `bytearray` são chamadas a seus construtores com:

* Uma `str` e um argumento nomeado `encoding`
* Um iterável que forneça itens com valores entre 0 e 255
* Um objeto que implemente o protocolo de buffer (por exemplo, `bytes`, `bytearray`, `memoryview`,
`array.array`), que copia os bytes do objeto fonte para a recém-criada sequência binária

[WARNING]
====
Até Python 3.5, era possível chamar `bytes` ou `bytearray` com um único inteiro,
para criar uma sequência daquele tamanho inicializada com bytes nulos.
Essa assinatura foi descontinuada no Python 3.5 e removida no Python 3.6.
Veja a https://fpy.li/pep467[PEP 467–Minor API improvements for binary sequences
(_Pequenas melhorias na API para sequências binárias_) ].
====

<<<
Criar uma sequência binária a partir de um objeto tipo buffer
é uma operação de baixo nível que pode envolver conversão de tipos.
Veja uma demonstração no <<ex_buffer_demo>>.

[[ex_buffer_demo]]
.Inicializando bytes a partir de dados brutos de um array
====
[source, python]
----
>>> import array
>>> numbers = array.array('h', [-2, -1, 0, 1, 2])  <1>
>>> octets = bytes(numbers)  <2>
>>> octets
b'\xfe\xff\xff\xff\x00\x00\x01\x00\x02\x00'  <3>
----
====
<1> O typecode `'h'` cria um `array` de _short integers_ (inteiros de 16 bits).
<2> `octets` mantém uma cópia dos bytes que compõem `numbers`.
<3> Esses são os 10 bytes que representam os 5 inteiros pequenos.

Criar um objeto `bytes` ou `bytearray` a partir de qualquer fonte tipo buffer vai sempre copiar os bytes.
Já objetos `memoryview` permitem compartilhar memória entre estruturas de dados binários, como vimos na <<memoryview_sec>>.

Após essa exploração básica dos tipos de sequências de bytes de Python,
vamos ver como eles são convertidos de e para strings.((("", startref="UTVbytes04")))

=== Codificadores/decodificadores básicos

A((("Unicode text versus bytes", "basic encoders/decoders", id="UTVbasic04")))((("encoding", "basics of",
id="encod04")))((("decoding", "basics of", id="decod04"))) distribuição de Python inclui mais de 100((("codecs")))
_codecs_ (_encoders/decoders_, codificadores/decodificadores) para conversão de texto para bytes e vice-versa.
Cada codec tem um nome, como `'utf_8'`, e às vezes apelidos, como `'utf8'`, `'utf-8'`, e `'U8'`,
que você pode usar como o argumento `encoding` em funções como
`open()`, `str.encode()`, `bytes.decode()`, e assim por diante.
O <<ex_codecs>> mostra o mesmo texto codificado como três sequências de bytes diferentes.

<<<
[[ex_codecs]]
.A string "El Niño" codificada com três codecs, gerando sequências de bytes muito diferentes
====
[source, python]
----
>>> for codec in ['latin_1', 'utf_8', 'utf_16']:
...     print(codec, 'El Niño'.encode(codec), sep='\t')
...
latin_1 b'El Ni\xf1o'
utf_8   b'El Ni\xc3\xb1o'
utf_16  b'\xff\xfeE\x00l\x00 \x00N\x00i\x00\xf1\x00o\x00'
----
====

A <<encodings_demo_fig>> mostra um conjunto de codecs gerando bytes a partir de caracteres
como a letra "A" e o símbolo musical da clave de sol.
Observe que as últimas três codificações usam múltplos bytes e tamanho variável.

[role="width-90"]
[[encodings_demo_fig]]
.Doze caracteres, seus pontos de código, e sua representação binária (em hexadecimal) em 7 codificações diferentes (asteriscos indicam que o caractere não pode ser representado naquela codificação).
image::../images/flpy_0401.png[Tabela de demonstração de codificações]

Todos aqueles asteriscos na <<encodings_demo_fig>> deixam claro que algumas codificações,
como o ASCII e mesmo o multi-byte GB2312, não conseguem representar todos os caracteres Unicode.
As codificações UTF, por outro lado, foram projetadas para lidar com todos os pontos de código possíveis.

Escolhi as codificações apresentadas na <<encodings_demo_fig>> como uma amostra representativa:

`latin1` a.k.a. `iso8859_1`:: Importante por ser a base de outras codificações, tal como a `cp1252` e o próprio Unicode
(observe que os valores binários do `latin1` aparecem nos bytes do `cp1252` e nos pontos de código).

`cp1252`:: Um superconjunto útil de `latin1`, criado pela Microsoft,
acrescentando símbolos convenientes como as aspas curvas e o € (euro);
alguns aplicativos de Windows chamam essa codificação de "ANSI", mas ela nunca foi um padrão ANSI real.

`cp437`:: O conjunto de caracteres original do IBM PC, com caracteres de desenho de caixas.
Incompatível com o `latin1`, que surgiu depois.

`gb2312`:: Padrão legado para codificar ideogramas chineses simplificados usados na República da China;
uma das várias codificações criadas para línguas asiáticas.

`utf-8`:: A codificação de 8 bits mais comum na web. Em julho de 2021, o
«_W³ Techs: Usage statistics of character encodings for websites_» [.small]#&#91;fpy.li/4-5&#93;#
informava que 97% dos sites usam UTF-8, um grande avanço sobre os 81,4% de setembro de 2014,
quando escrevi este capítulo na primeira edição.

`utf-16le`:: Uma forma do esquema de codificação UTF de 16 bits; todas as codificações UTF-16
suportam pontos de código acima de U+FFFF, através de sequências de escape chamadas "pares substitutos".


[WARNING]
====
A UTF-16((("UCS-2 encoding")))((("emojis", "UCS-2 versus UTF-16 encoding")))
sucedeu a codificação de 16 bits original do Unicode 1.0—a UCS-2—há muito tempo, em 1996.
Mas a UCS-2 obsoleta ainda é usada em muitos sistemas, apesar de ter sido descontinuada
por suportar apenas pontos de código até U+FFFF.
Em 2021, mais de 57% dos pontos de código alocados ficam acima de U+FFFF,
incluindo os emojis.
====

Após completar essa revisão das codificações mais comuns,
vamos agora tratar das questões relativas a operações de codificação e
decodificação.((("", startref="UTVbasic04")))((("", startref="encod04")))((("", startref="decod04")))


=== Entendendo os problemas de codificação/decodificação

Apesar((("Unicode text versus bytes", "understanding encode/decode problems", 
id="UTVunder04")))((("encoding", "understanding encode/decode problems",
id="Eunderst04")))((("decoding", "understanding encode/decode problems", id="Dunder04")))
de existir uma exceção genérica, `UnicodeError`,
o erro relatado pelo Python em geral é mais específico:
ou é um `UnicodeEncodeError` (ao converter uma `str` para sequências binárias)
ou é um `UnicodeDecodeError` (ao ler uma sequência binária para uma `str`).
Carregar módulos de Python também pode gerar um `SyntaxError`,
quando a codificação da fonte for inesperada.
Vamos ver como tratar todos esses erros nas próximas seções.

[role="man-height3"]
[TIP]
====
A primeira coisa a observar quando aparece um erro de Unicode é o tipo exato da exceção.
É um `UnicodeEncodeError`, um `UnicodeDecodeError`, ou algum outro erro (por exemplo, `SyntaxError`)
mencionando um problema de codificação?
Para resolver o problema, você primeiro precisa entendê-lo.
====


==== Tratando o UnicodeEncodeError

A maioria((("UnicodeEncodeError"))) dos codecs não-UTF entendem apenas um pequeno subconjunto dos caracteres Unicode.
Ao converter texto para bytes, um `UnicodeEncodeError` será gerado se
um caractere não estiver definido na codificação alvo,
a menos que seja fornecido um tratamento especial,
passando um argumento `errors` para o método ou função de codificação.
O comportamento para tratamento de erro é apresentado no <<ex_encoding>>.

[[ex_encoding]]
.Codificando para bytes, e tratamento de erros
====
[source, python]
----
>>> city = 'São Paulo'
>>> city.encode('utf_8')  <1>
b'S\xc3\xa3o Paulo'
>>> city.encode('utf_16')
b'\xff\xfeS\x00\xe3\x00o\x00 \x00P\x00a\x00u\x00l\x00o\x00'
>>> city.encode('iso8859_1')  <2>
b'S\xe3o Paulo'
>>> city.encode('cp437')  <3>
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/.../lib/python3.4/encodings/cp437.py", line 12, in encode
    return codecs.charmap_encode(input,errors,encoding_map)
UnicodeEncodeError: 'charmap' codec can't encode character '\xe3' in
position 1: character maps to <undefined>
>>> city.encode('cp437', errors='ignore')  <4>
b'So Paulo'
>>> city.encode('cp437', errors='replace')  <5>
b'S?o Paulo'
>>> city.encode('cp437', errors='xmlcharrefreplace')  <6>
b'S&#227;o Paulo'
----
====
<1> As codificações UTF lidam com qualquer `str`
<2> `iso8859_1` também funciona com a string `'São Paulo'`.
<3> `cp437` não consegue codificar o `'ã'` ("a" com til).
O método default de tratamento de erro (`'strict'`) gera um `UnicodeEncodeError`.
<4> O método de tratamento `errors='ignore'` pula os caracteres que não podem ser codificados;
isso normalmente é uma péssima ideia, levando a perda silenciosa de informação.
<5> Ao codificar, `errors='replace'` substitui os caracteres não-codificáveis por um `'?'`;
há perda de informação, mas é mais fácil notar que algo está errado.
<6> `'xmlcharrefreplace'` substitui os caracteres não-codificáveis por uma entidade XML.
Se não pode usar UTF e não pode perder informação, essa é a opção.

[NOTE]
====
O tratamento de erros de `codecs` é extensível.
Você pode registrar novas strings para o argumento `errors`
passando um nome e uma função de tratamento de erros para a função `codecs.register_error`.
Veja «documentação de  `codecs.register_error`» [.small]#&#91;fpy.li/39&#93;#.
====

O ASCII é um subconjunto comum a todas as codificações que conheço,
então a codificação deveria sempre funcionar se o texto for composto exclusivamente por caracteres ASCII.
Python 3.7 trouxe um novo método booleano, «`str.isascii()`» [.small]#&#91;fpy.li/4-7&#93;#,
para verificar se seu texto Unicode é 100% ASCII.
Se for, você deve ser capaz de codificá-lo para bytes em qualquer codificação sem gerar um `UnicodeEncodeError`.


[[decode_error_sec]]
==== Tratando o UnicodeDecodeError

Nem((("UnicodeDecodeError"))) todo byte contém um caractere ASCII válido,
e nem toda sequência de bytes é um texto corretamente codificado em UTF-8 ou UTF-16;
assim, se você presumir uma dessas codificações ao converter uma sequência binária para texto,
pode receber um `UnicodeDecodeError`, se bytes inesperados forem encontrados.

Por outro lado, várias codificações de 8 bits antigas, como a `'cp1252'`, a `'iso8859_1'` e a `'koi8_r'`
são capazes de decodificar qualquer série de bytes, incluindo ruído aleatório, sem reportar qualquer erro.
Portanto, se seu programa presumir a codificação de 8 bits errada, ele vai decodificar lixo silenciosamente.

.Gremlins, mojibake, e tofu
[TIP]
====
Caracteres trocados ou distorcidos são conhecidos como "gremlins" ou "mojibake"
("caractere transformado", em japonês).
Outro defeito comum ocorre quando a codificação está certa,
mas a fonte não tem o glifo (desenho) de um caractere.
Neste caso ele aparece como um retângulo branco apelidado de "tofu"
entre os especialistas em fontes.footnote:[Agradeço ao Felipe Sanches do Garoa Hacker Clube por
me ensinar o termo "tofu", e me contar que a «coleção de fontes Noto» [.small]#&#91;fpy.li/5s&#93;#
é um projeto permanente para juntar todos os glifos necessários para evitar tofu em textos Unicode.
"Noto" refere-se ao ideal "no tofu" (sem tofu).]
Por exemplo, a palavra mojibake em japonês pode aparecer como quatro tofus: 文字化け.
Aqui, o motivo é que o programa `asciidoctor-pdf` que usei para gerar o livro impresso
usa um subconjunto da fonte Noto que não tem glifos kanji.
Lamento essa falha técnica. Pesquisei muito, mas não consegui resolver a tempo.
Pelo menos a versão HTML está sem tofu, nos principais navegadores modernos.
Confira «esta mesma seção lá no PythonFluente.com» [.small]#&#91;fpy.li/5r&#93;#.
====

O <<ex_decoding>> ilustra como o uso do codec errado pode produzir gremlins ou um `UnicodeDecodeError`.

[[ex_decoding]]
.Decodificando de `str` para bytes: sucesso e tratamento de erro
====
[source, python]
----
>>> octets = b'Montr\xe9al'  <1>
>>> octets.decode('cp1252')  <2>
'Montréal'
>>> octets.decode('iso8859_7')  <3>
'Montrιal'
>>> octets.decode('koi8_r')  <4>
'MontrИal'
>>> octets.decode('utf_8')  <5>
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 5:
invalid continuation byte
>>> octets.decode('utf_8', errors='replace')  <6>
'Montr�al'
----
====
<1> A palavra "Montréal" codificada em `latin1`; `'\xe9'` é o byte para "é".
<2> Decodificar com Windows 1252 funciona, pois esse codec é um superconjunto de `latin1`.
<3> ISO-8859-7 foi projetado para a língua grega, então o byte `'\xe9'` é interpretado incorretamente,
mas nenhum erro é gerado.
<4> KOI8-R foi projetado para o russo.
Agora `'\xe9'` significa a letra "И" do alfabeto cirílico.
<5> O codec `'utf_8'` detecta que `octets` não é UTF-8 válido, e gera um `UnicodeDecodeError`.
<6> Usando `'replace'` para tratamento de erro, o `\xe9` é substituído por "�"
(ponto de código #U+FFFD), o caractere oficial do Unicode chamado `REPLACEMENT CHARACTER`,
criado exatamente para representar caracteres desconhecidos.

[[syntax_error_encoding]]
==== SyntaxError ao carregar módulos com codificação inesperada

UTF-8((("SyntaxError"))) é a codificação default para código-fonte no Python 3,
da mesma forma que ASCII era o default no Python 2.
Se você carregar um módulo _.py_ contendo dados que não estejam em UTF-8,
sem declaração codificação, receberá uma mensagem como essa:

----
include::../code/04-text-byte/syntax-msg.txt[]
----

Como o UTF-8 está amplamente instalado em sistemas GNU/Linux e macOS,
um cenário onde isso tem mais chance de ocorrer é na abertura de um
arquivo _.py_ criado no Windows, com `cp1252`.
Observe que esse erro ocorre mesmo no Python para Windows,
pois a codificação default para fontes de Python 3 é UTF-8 em todas as plataformas.

Para resolver esse problema, acrescente o comentário mágico `coding` no início do arquivo, como no <<ex_ola_mundo>>.

[[ex_ola_mundo]]
.'ola.py': um "Hello, World!" em português
====
[source, python]
----
# coding: cp1252

print('Olá, Mundo!')
----
====

[TIP]
====
Agora que o código-fonte de Python 3 não está mais limitado ao ASCII,
e por padrão usa a excelente codificação UTF-8,
a melhor "solução" para código-fonte em codificações antigas
como `'cp1252'` é converter tudo para UTF-8 de uma vez,
e não se preocupar com os comentários `coding`.
Se seu editor não suporta UTF-8, é hora de trocar de editor.
====

Suponha que você tem um arquivo de texto, seja ele código-fonte ou poesia,
mas não sabe qual codificação foi usada.
Como detectar a codificação correta? Respostas na próxima seção.


[[discover_encoding]]
==== Como descobrir a codificação de uma sequência de bytes

Como((("byte sequences"))) descobrir a codificação de uma sequência de bytes?
Resposta curta: não é possível.
Você precisa ser informado.

Alguns protocolos de comunicação e formatos de arquivo, como o HTTP e o XML,
contêm cabeçalhos que nos dizem explicitamente como o conteúdo está codificado.
Você pode ter certeza que algumas sequências de bytes não estão em ASCII,
pois elas contêm bytes com valores acima de 127,
e o modo como o UTF-8 e o UTF-16 são construídos também limita as sequências de bytes possíveis.

<<<
.O hack do Leo para detectar codificação UTF-8
****
(Os próximos parágrafos vieram de uma nota escrita pelo revisor técnico Leonardo Rochael no rascunho deste livro.)

Pela((("UTF-8 decoding"))) forma como o UTF-8 foi projetado,
é quase impossível que uma sequência aleatória de bytes,
ou mesmo uma sequência não-aleatória de bytes de uma codificação diferente do UTF-8,
seja acidentalmente decodificada como lixo no UTF-8, ao invés de gerar um `UnicodeDecodeError`.

As razões para isso são que as sequências de escape do UTF-8 nunca usam caracteres ASCII,
e tais sequências de escape tem padrões de bits que tornam muito difícil que dados aleatórios sejam UTF-8 válido por acidente.
Portanto, se você consegue decodificar como UTF-8 alguns bytes com valor maior que 127,
é altamente provável que o texto esteja em UTF-8.

Trabalhando com os serviços online brasileiros, alguns baseados em back-ends antigos,
ocasionalmente precisei implementar uma estratégia de decodificação que tentava decodificar via UTF-8
e tratava um `UnicodeDecodeError` decodificando via `cp1252`.
É feio, mas funciona.
****

Entretanto, considerando que as linguagens humanas também tem suas regras e restrições,
uma vez que você supõe que uma série de bytes é um((("plain text"))) texto puro,
pode ser possível intuir sua codificação usando heurística e estatística.
Por exemplo, se bytes com valor `b'\x00'` bytes forem comuns,
é provável que seja uma codificação de 16 ou 32 bits, e não um esquema de 8 bits,
pois caracteres nulos em texto simples são erros.
Quando a sequência de bytes \`b'\x20\x00'` aparece com frequência,
é mais provável que esse seja o caractere de espaço (U+0020) na codificação UTF-16LE,
e não o obscuro caractere U+2000 (`EN QUAD`)—seja lá o que for isso.

É((("Chardet library"))) assim que o pacote
«_Chardet–The Universal Character Encoding Detector_ (Chardet—O Detector Universal de Codificações de Caracteres)»
[.small]#&#91;fpy.li/4-8&#93;#
faz para descobrir cada uma das mais de 30 codificações suportadas.
_Chardet_ é uma biblioteca Python que pode ser usada em seus programas,
mas que também inclui um utilitário de linha de comando, `chardetect`.
Veja como ele analisa o código-fonte desse capítulo:

[source,bash]
----
$ chardetect cap04.adoc
cap04.adoc: utf-8 with confidence 0.99
----

Apesar de sequências binárias de texto codificado normalmente não trazerem dicas sobre sua codificação,
os formatos UTF podem usar um marcador de ordem dos bytes.
Isso é explicado a seguir.


==== BOM: um gremlin útil

No((("BOMs (byte-order marks)"))) <<ex_codecs>>,
você pode ter notado um par de bytes extra no início de uma sequência codificada em UTF-16.
Aqui estão eles novamente:

[source, python]
----
>>> u16 = 'El Niño'.encode('utf_16')
>>> u16
b'\xff\xfeE\x00l\x00 \x00N\x00i\x00\xf1\x00o\x00'
----

Os bytes são `b'\xff\xfe'`. Isso é um __BOM__—sigla para byte-order mark
(marcador de ordem de bytes)—indicando a ordenação de bytes "little-endian" da CPU Intel onde a codificação foi realizada.

Em uma máquina _little-endian_, para cada ponto de código, o byte menos significativo aparece primeiro:
a letra `'E'`, ponto de código U+0045 (decimal 69), é codificado nas posições 2 e 3 dos bytes como `69` e `0`:

[source, python]
----
>>> list(u16)
[255, 254, 69, 0, 108, 0, 32, 0, 78, 0, 105, 0, 241, 0, 111, 0]
----

Em uma CPU _big-endian_, os bytes são trocados: `'E'` é codificado como `0` e `69`.

Para evitar confusão, a codificação UTF-16 precede o texto a ser codificado com
o caractere especial invisível `ZERO WIDTH NO-BREAK SPACE` (U+FEFF).
Em um sistema _little-endian_, isso é codificado como `b'\xff\xfe'` (decimais 255, 254).
Como, por design, não existe um caractere U+FFFE em Unicode,
a sequência de bytes `b'\xff\xfe'` tem que ser o `ZERO WIDTH NO-BREAK SPACE` em uma codificação _little-endian_,
e então o codec sabe qual ordenação de bytes usar.

Há uma variante do UTF-16–o UTF-16LE–que é explicitamente _little-endian_,
e outra que é explicitamente _big-endian_, o UTF-16BE.
Se você os usar, um BOM não será gerado:

[source, python]
----
>>> u16le = 'El Niño'.encode('utf_16le')
>>> list(u16le)
[69, 0, 108, 0, 32, 0, 78, 0, 105, 0, 241, 0, 111, 0]
>>> u16be = 'El Niño'.encode('utf_16be')
>>> list(u16be)
[0, 69, 0, 108, 0, 32, 0, 78, 0, 105, 0, 241, 0, 111]
----

Se o BOM estiver presente, supõe-se que ele será filtrado pelo codec UTF-16,
então recebemos apenas o conteúdo textual efetivo do arquivo,
sem o `ZERO WIDTH NO-BREAK SPACE` inicial.

O padrão Unicode diz que se um arquivo é UTF-16 e não tem um BOM,
deve-se presumir que ele é UTF-16BE (_big-endian_).
Entretanto, a arquitetura x86 da Intel é _little-endian_,
daí que há uma grande quantidade de UTF-16 _little-endian_ e sem BOM no mundo.

Toda essa questão de ordenação dos bytes (_endianness_)
só afeta codificações que usam palavras de máquina com mais de um byte, como UTF-16 e UTF-32.
Uma grande vantagem do UTF-8 é produzir a mesma sequência independente da ordenação dos bytes,
então um BOM não é necessário.
No entanto, algumas aplicações Windows (em especial o Notepad) mesmo assim acrescentam o BOM
a arquivos UTF-8—e o Excel depende do BOM para detectar um arquivo UTF-8,
caso contrário ele presume que o conteúdo está codificado com uma página de código do Windows.
Essa codificação UTF-8 com BOM é chamada((("UTF-8-SIG encoding"))) UTF-8-SIG no registro de codecs de Python.
O caractere U+FEFF codificado em UTF-8-SIG é a sequência de três bytes `b'\xef\xbb\xbf'`.
Então, se um arquivo começa com aqueles três bytes, é provavelmente um arquivo UTF-8 com um BOM.

[role="man-height-2-25"]
.A dica de Caleb sobre o UTF-8-SIG
[TIP]
====
Caleb Hattingh—um dos revisores técnicos—sugere sempre usar o codec UTF-8-SIG para ler arquivos UTF-8.
Isso é inofensivo, pois o UTF-8-SIG lê corretamente arquivos com ou sem um BOM, e não devolve o BOM propriamente dito.
Para escrever arquivos, recomendo usar UTF-8, para interoperabilidade integral.
Por exemplo, scripts Python podem ser tornados executáveis em sistemas Unix,
se começarem com o comentário: `\#!/usr/bin/env python3`.
Os dois primeiros bytes do arquivo precisam ser `+b'#!'+` para isso funcionar, mas o BOM quebra essa convenção.
Se você tem o requerimento específico de exportar dados para aplicativos que precisam do BOM,
use o UTF-8-SIG, mas esteja ciente do que diz a
«documentação sobre codecs» [.small]#&#91;fpy.li/3a&#93;# 
de Python:
"No UTF-8, o uso do BOM é desencorajado e, em geral, deve ser evitado."
====

Vamos agora ver como tratar arquivos de texto no
Python 3.((("", startref="UTVunder04")))((("", startref="Eunderst04")))((("", startref="Dunder04")))


=== Processando arquivos de texto

[[unicode_sandwich_fig]]
.O sanduíche de Unicode: a melhor prática para processamento de texto.
image::../images/flpy_0402.png[align="center",pdfwidth=11cm]

A((("Unicode text versus bytes", "handling text files", id="UTVtext04")))((("text files, handling", id="Tfile04")))
melhor prática para lidar com E/S de texto é o((("Unicode sandwich"))) "Sanduíche de Unicode" (_Unicode  sandwich_)
(<<unicode_sandwich_fig>>).footnote:[A primeira vez que vi o termo "Unicode sandwich" (_sanduíche de Unicode_)
foi na excelente apresentação de Ned Batchelder, «_Pragmatic Unicode_» [.small]#&#91;fpy.li/4-10&#93;# na US PyCon 2012.]
Isso significa que os `bytes` devem ser decodificados para `str` o mais cedo possível na entrada
(por exemplo, ao abrir um arquivo para leitura).
O "recheio" do sanduíche é a lógica do negócio de seu programa,
onde o tratamento do texto é feito somente com objetos `str`.
Evite codificar ou decodificar em diferentes estágios do processamento.
Na saída, as `str` são codificadas para `bytes` o mais tarde possível.
A maioria dos frameworks web funciona assim, e raramente tocamos em `bytes` ao usá-los.
No Django, por exemplo, suas views devem produzir `str` em Unicode;
o próprio Django se encarrega de codificar a resposta para `bytes`, usando UTF-8 como default.

Python 3 torna mais fácil seguir o conselho do sanduíche de Unicode,
pois a função `open()` decodifica na leitura e
a codifica na escrita, ao lidar com arquivos em modo texto.
Dessa forma, tudo que você recebe de `my_file.read()` e passa para `my_file.write(text)` são objetos `str`.

Assim, usar arquivos de texto é aparentemente simples.
Mas se você confiar nas codificações default, pode acabar levando uma mordida.

Observe a sessão de console no <<ex_cafe_file1>>. Você consegue ver o erro?

[[ex_cafe_file1]]
.Uma questão de plataforma na codificação (você pode ou não ver o problema se tentar isso na sua máquina)
====
[source, python]
----
>>> open('cafe.txt', 'w', encoding='utf_8').write('café')
4
>>> open('cafe.txt').read()
'cafÃ©'
----
====

O erro: especifiquei a codificação UTF-8 ao escrever o arquivo, mas não fiz isso na leitura,
então Python assumiu a codificação de arquivo default do Windows—página de código 1252—e
os bytes finais foram decodificados como os caracteres `'Ã©'` ao invés de `'é'`.

Executei o <<ex_cafe_file1>> no Python 3.8.1, 64 bits, no Windows 10 (build 18363).
Os mesmos comandos rodando em um GNU/Linux ou um macOS recentes funcionam perfeitamente,
pois a codificação default desses sistemas é UTF-8, dando a falsa impressão que tudo está bem.
Se o argumento de codificação fosse omitido ao abrir o arquivo para escrita,
a codificação default do _locale_ seria usada, e poderíamos ler o arquivo corretamente usando a mesma codificação.
Mas aí o script geraria arquivos com bytes diferentes dependendo da plataforma,
ou mesmo das configurações do _locale_ na mesma plataforma, criando problemas de compatibilidade.

[TIP]
====
Código que precisa rodar em múltiplas máquinas ou múltiplas ocasiões não deve depender de defaults de codificação.
Sempre passe um argumento `encoding=` explícito ao abrir arquivos de texto,
pois o default pode mudar de uma máquina para outra ou de um dia para o outro.
====

Um detalhe curioso no <<ex_cafe_file1>> é que a função `write` na primeira instrução informa que
foram escritos quatro caracteres, mas na linha seguinte são lidos cinco caracteres.
O <<ex_cafe_file2>> é uma versão estendida do <<ex_cafe_file1>>, e explica esse e outros detalhes.

[[ex_cafe_file2]]
.Uma inspeção mais atenta do <<ex_cafe_file1>> rodando no Windows revela o bug e a solução do problema
====
[source, python]
----
>>> fp = open('cafe.txt', 'w', encoding='utf_8')
>>> fp  # <1>
<_io.TextIOWrapper name='cafe.txt' mode='w' encoding='utf_8'>
>>> fp.write('café')  # <2>
4
>>> fp.close()
>>> import os
>>> os.stat('cafe.txt').st_size  # <3>
5
>>> fp2 = open('cafe.txt')
>>> fp2  # <4>
<_io.TextIOWrapper name='cafe.txt' mode='r' encoding='cp1252'>
>>> fp2.encoding  # <5>
'cp1252'
>>> fp2.read() # <6>
'cafÃ©'
>>> fp3 = open('cafe.txt', encoding='utf_8')  # <7>
>>> fp3
<_io.TextIOWrapper name='cafe.txt' mode='r' encoding='utf_8'>
>>> fp3.read() # <8>
'café'
>>> fp4 = open('cafe.txt', 'rb')  # <9>
>>> fp4                           # <10>
<_io.BufferedReader name='cafe.txt'>
>>> fp4.read()  # <11>
b'caf\xc3\xa9'
----
====
<1> Por padrão, `open` usa o modo texto e devolve um objeto `TextIOWrapper` com uma codificação específica.
<2> O método `write` de um `TextIOWrapper` devolve o número de caracteres Unicode escritos.
<3> `os.stat` diz que o arquivo tem 5 bytes; o UTF-8 codifica `'é'` com 2 bytes, 0xc3 e 0xa9.
<4> Abrir um arquivo de texto sem uma codificação explícita devolve um `TextIOWrapper`
com a codificação configurada para um default do locale.
<5> Um objeto `TextIOWrapper` tem um atributo de codificação que pode ser inspecionado: neste caso, `cp1252`.
<6> Na codificação `cp1252` do Windows, o byte 0xc3 é um "Ã" (A maiúsculo com til),
e 0xa9 é o símbolo de copyright.
<7> Abrindo o mesmo arquivo com a codificação correta.
<8> O resultado esperado: os mesmos quatro caracteres Unicode para `'café'`.
<9> A flag `'rb'` abre um arquivo para leitura em modo binário.
<10> O objeto devolvido é um `BufferedReader`, e não um `TextIOWrapper`.
<11> Ler do arquivo obtém bytes, como esperado.

[TIP]
====
Não abra arquivos de texto no modo binário,
a menos que seja necessário analisar o conteúdo do arquivo para determinar sua codificação—e mesmo assim,
você deveria estar usando o Chardet em vez de reinventar a roda (veja a <<discover_encoding>>).

Programas comuns só deveriam usar o modo binário para abrir arquivos binários, como arquivos de imagens raster ou bitmaps.
====

O problema no <<ex_cafe_file2>> vem de se confiar numa configuração default ao se abrir um arquivo de texto.
Há várias fontes de tais defaults, como mostra a próxima seção.

[[encoding_defaults]]
==== Cuidado com os defaults de codificação

Várias((("encoding", "encoding defaults", id="Edefault04")))
configurações afetam os defaults de codificação para E/S no Python.
Veja o script __default_encodings.py__ script no <<ex_default_encodings>>.

<<<
[[ex_default_encodings]]
.Explorando os defaults de codificação
====
[source, python]
----
include::../code/04-text-byte/default_encodings.py[]
----
====

A saída do <<ex_default_encodings>> no GNU/Linux (Ubuntu 14.04 a 19.10)
e no macOS (10.9 a 10.14) é idêntica, mostrando que `UTF-8` é usado em toda parte nesses sistemas:

[source]
----
$ python3 default_encodings.py
 locale.getpreferredencoding() -> 'UTF-8'
                 type(my_file) -> <class '_io.TextIOWrapper'>
              my_file.encoding -> 'UTF-8'
           sys.stdout.isatty() -> True
           sys.stdout.encoding -> 'utf-8'
            sys.stdin.isatty() -> True
            sys.stdin.encoding -> 'utf-8'
           sys.stderr.isatty() -> True
           sys.stderr.encoding -> 'utf-8'
      sys.getdefaultencoding() -> 'utf-8'
   sys.getfilesystemencoding() -> 'utf-8'
----

No Windows, porém, a saída é o <<ex_default_encodings_ps>>.

[[ex_default_encodings_ps]]
.Codificações default, no PowerShell do Windows 10 (a saída é a mesma no cmd.exe)
====
[source]
----
> chcp  <1>
Active code page: 437
> python default_encodings.py  <2>
 locale.getpreferredencoding() -> 'cp1252'  <3>
                 type(my_file) -> <class '_io.TextIOWrapper'>
              my_file.encoding -> 'cp1252'  <4>
           sys.stdout.isatty() -> True      <5>
           sys.stdout.encoding -> 'utf-8'   <6>
            sys.stdin.isatty() -> True
            sys.stdin.encoding -> 'utf-8'
           sys.stderr.isatty() -> True
           sys.stderr.encoding -> 'utf-8'
      sys.getdefaultencoding() -> 'utf-8'
   sys.getfilesystemencoding() -> 'utf-8'
----
====
<1> `chcp` mostra a página de código ativa para o console: `437`.
<2> Executando __default_encodings.py__, com a saída direcionada para o console.
<3> `locale.getpreferredencoding()` é a configuração mais importante.
<4> Arquivos de texto usam`locale.getpreferredencoding()` como default.
<5> A saída está direcionada para o console, então `sys.stdout.isatty()` é `True`.
<6> Agora, `sys.stdout.encoding` não é a mesma que a página de código informada por `chcp`!

O suporte a Unicode no próprio Windows e no Python para Windows melhorou desde que escrevi a primeira edição deste livro.
O <<ex_default_encodings_ps>> costumava informar quatro codificações diferentes no Python 3.4 rodando no Windows 7.
As codificações para `stdout`, `stdin`, e `stderr` costumavam ser
iguais à da página de código ativa informada pelo comando `chcp`,
mas agora são todas `utf-8`, graças à
«_PEP 528–Change Windows console encoding to UTF-8_ (Mudar a codificação do console no Windows para UTF-8)»
[.small]#&#91;fpy.li/pep528&#93;#, implementada no Python 3.6, e ao suporte a Unicode no PowerShell do _cmd.exe_
(desde o Windows 1809, de outubro de 2018).footnote:[Fonte: 
https://fpy.li/4-11["Windows Command-Line: Unicode and UTF-8 Output Text Buffer"
(_A Linha de Comando do Windows: O Buffer de Saída de Texto para Unicode e UTF-8_)].]
É esquisito que o `chcp` e o `sys.stdout.encoding` reportem coisas diferentes quando o `stdout`
está escrevendo no console,
mas é ótimo podermos agora escrever strings Unicode sem erros de codificação no Windows—a menos que
o usuário redirecione a saída para um arquivo, como veremos adiante.
Isso não significa que todos os seus emojis((("emojis", "console font and")))
favoritos vão aparecer: isso também depende da fonte usada pelo console.

Outra mudança foi a
«_PEP 529–Change Windows filesystem encoding to UTF-8_ (Mudar a codificação do sistema de arquivos do Windows para UTF-8)»
[.small]#&#91;fpy.li/pep529&#93;#,
também implementada no Python 3.6,
que mudou a codificação do sistema de arquivos (usada para representar nomes de diretórios e de arquivos),
da codificação proprietária MBCS da Microsoft para UTF-8.

Entretanto, se a saída do <<ex_default_encodings>> for redirecionada para um arquivo, assim...

[source]
----
Z:\>python default_encodings.py > encodings.log
----

...aí o valor de `sys.stdout.isatty()` se torna `False`, e `sys.stdout.encoding`
é determinado por `locale.getpreferredencoding()`,
`'cp1252'` naquela máquina—mas `sys.stdin.encoding` e `sys.stderr.encoding` seguem como  `utf-8`.


[TIP]
====
No((("&#x005C;N&#x007B;&#x007D; (Unicode literals escape notation)")))((("Unicode literals escape notation (&#x005C;N&#x007B;&#x007D;)")))
<<ex_stdout_check>>, usei a expressão de escape `'\N{}'` para literais Unicode,
escrevendo o nome oficial do caractere dentro do `\N{}`.
Isso é bastante prolixo, mas explícito e seguro:
Python gera um `SyntaxError` se o nome não existir—bem melhor que escrever um número hexadecimal que pode estar errado,
mas isso só será descoberto mais tarde.
De qualquer forma, você provavelmente vai querer escrever um comentário explicando os códigos numéricos dos caracteres,
então a verbosidade do `\N{}` é fácil de aceitar.
====

Isso significa que um script como o <<ex_stdout_check>> funciona quando está escrevendo no console,
mas pode falhar quando a saída é redirecionada para um arquivo.

<<<
[[ex_stdout_check]]
.stdout_check.py
====
[source, python]
----
include::../code/04-text-byte/stdout_check.py[]
----
====

O <<ex_stdout_check>> mostra o resultado de uma chamada a `sys.stdout.isatty()`,
o valor de `sys.stdout.encoding`, e esses três caracteres:

* `'…'` `HORIZONTAL ELLIPSIS`: existe no CP 1252, mas não no CP 437.
* `'∞'` `INFINITY`: existe no CP 437, mas não no CP 1252 (perdão pelo tofu). 
* `'㊷'` `CIRCLED NUMBER FORTY TWO`: não existe nem no CP 1252 nem no CP 437 (tofu).

[WARNING]
====
Este livro foi escrito em UTF-8, portanto em tese posso usar qualquer caractere Unicode.
Porém, a fonte padrão do Asciidoc para gerar o PDF não tem todos os caracteres Unicode.
Não achei alguns caracteres em outra fonte compatível,
então no livro impresso aparecem como tofu.
Mas no HTML, está tudo certo.
Veja este mesmo exemplo no «PythonFluente.com»
[.small]#&#91;fpy.li/5t&#93;#.
====

Quando executo _stdout_check.py_ no PowerShell ou no _cmd.exe_, funciona como visto na <<fig_stdout_check>>.

[[fig_stdout_check]]
.Executando _stdout_check.py_ no PowerShell.
image::../images/flpy_0403.png[Captura de tela do `stdout_check.py` no PowerShell]

[role="pagebreak-before less_space"]
Apesar de `chcp` informar o código ativo como 437, `sys.stdout.encoding` é UTF-8,
então tanto `HORIZONTAL ELLIPSIS` quanto `INFINITY` são escritos corretamente.
O `CIRCLED NUMBER FORTY TWO` é substituído por um retângulo, mas nenhum erro é gerado.
Presume-se que ele seja reconhecido como um caractere válido, mas a fonte do console não tem o glifo para mostrá-lo.

Entretanto, quando redireciono a saída de _stdout_check.py_ para um arquivo, o resultado é o da <<fig_stdout_check_redir>>.

[[fig_stdout_check_redir]]
.Executanto _stdout_check.py_ no PowerShell, redirecionando a saída.
image::../images/flpy_0404.png["Captura de tela do `stdout_check.py` no PowerShell, redirecionando a saída"]

O primeiro problema demonstrado pela <<fig_stdout_check_redir>> é o `UnicodeEncodeError` mencionando o caractere `'\u221e'`,
porque  `sys.stdout.encoding` é `'cp1252'`—uma página de código que não tem o caractere `INFINITY`.

Lendo _out.txt_ com o comando `type`—ou um editor de Windows como o VS Code ou o Sublime Text—mostra que,
ao invés do HORIZONTAL ELLIPSIS, consegui um `'à'` (`LATIN SMALL LETTER A WITH GRAVE`).
Acontece que o valor binário 0x85 no CP 1252 significa `'…'`, mas no CP 437 o mesmo valor binário representa o `'à'`.
Então, pelo visto, a página de código ativa tem alguma importância, não de uma forma razoável ou útil,
mas como uma explicação parcial para uma experiência ruim com o Unicode.


[NOTE]
====
Para realizar esses experimentos, usei um laptop configurado para o mercado norte-americano,
rodando Windows 10 OEM.
Versões de Windows localizadas para outros países podem ter configurações de codificação diferentes.
No Brasil, por exemplo, o console do Windows usa a página de código 850 por padrão—e não a 437.
====

Para encerrar esse enlouquecedor tópico de codificações default,
vamos dar uma última olhada nas diferentes codificações no <<ex_default_encodings_ps>>:

* Se você omitir o argumento `encoding` ao abrir um arquivo,
o default é dado por `locale.getpreferredencoding()` (`'cp1252'` no <<ex_default_encodings_ps>>).

* Antes de Python 3.6, a codificação de `sys.stdout|stdin|stderr`
costumava ser determinada pela variável do ambiente
«`PYTHONIOENCODING`» [.small]#&#91;fpy.li/3b&#93;#—agora
essa variável é ignorada, a menos que
«`PYTHONLEGACYWINDOWSSTDIO`» [.small]#&#91;fpy.li/3c&#93;#
seja definida como uma string não-vazia.
Caso contrário, a codificação da E/S padrão será UTF-8 para E/S interativa, ou definida por
`locale.getpreferredencoding()`, se a entrada e a saída forem redirecionadas para ou de um arquivo.

* `sys.getdefaultencoding()` é usado internamente pelo Python em conversões implícitas de
dados binários de ou para `str`. Não há suporte para mudar essa configuração.

* `sys.getfilesystemencoding()` é usado para codificar/decodificar nomes de arquivo
(mas não o conteúdo dos arquivos).
Ele é usado quando `open()` recebe um argumento `str` para um nome de arquivo;
se o nome do arquivo é passado como um argumento `bytes`,
ele é entregue sem modificação para a API do sistema operacional.

<<<
[NOTE]
====
Há muitos anos, no GNU/Linux e no macOS, todas essas codificações são definidas como UTF-8 por padrão,
então a E/S entende e exibe todos os caracteres Unicode.
No Windows, não apenas codificações diferentes são usadas no mesmo sistema,
elas também são, normalmente, páginas de código como `'cp850'` ou `'cp1252'`, que suportam só o ASCII
com 127 caracteres adicionais (que por sua vez são diferentes de uma codificação para a outra).
Assim, usuários de Windows têm mais chances de encontrar erros de codificação.
====

Resumindo, a configuração de codificação mais importante devolvida por `locale.getpreferredencoding()`
é a default para abrir arquivos de texto e para `sys.stdout/stdin/stderr`,
quando eles são redirecionados para arquivos.
Entretanto, a
«documentação» [.small]#&#91;fpy.li/3d&#93;# diz (em parte):

[quote]
____
`locale.getpreferredencoding(do_setlocale=True)`:: Retorna a codificação da localidade usada para dados de texto,
de acordo com as preferências do usuário.
As preferências do usuário são expressas de maneira diferente em sistemas diferentes e
podem não estar disponíveis programaticamente em alguns sistemas,
portanto, essa função retorna apenas um palpite. [...]
____

Assim, o melhor conselho sobre defaults de codificação é: não confie neles.

Você evitará muitas dores de cabeça se seguir o conselho do sanduíche de Unicode,
e sempre tratar codificações de forma explícita em seus programas.
Infelizmente, o Unicode é trabalhoso mesmo se você converter seus `bytes` para `str` corretamente.
As duas próximas seções tratam de assuntos que são simples no reino do ASCII,
mas ficam muito complexos no planeta Unicode: normalização de texto
(isto é, transformar o texto em uma representação uniforme para comparações)
e ordenação.((("", startref="Edefault04")))((("", startref="Tfile04")))((("", startref="UTVtext04")))

<<<
[[normalizing_unicode]]
=== Normalizando o Unicode para comparações

Comparações de strings((("Unicode text versus bytes", "normalizing Unicode for reliable comparisons",
id="UTVnormal04")))((("strings", "normalizing Unicode for reliable comparisons", id="Snormal04")))
são dificultadas pelo fato do Unicode permitir combinações de caracteres:
sinais diacríticos e outras marcações que são sobrepostas ao caractere anterior,
ambos aparecendo juntos como um só caractere quando impressos.

Por exemplo, a palavra "café" pode ser composta de duas formas,
usando quatro ou cinco pontos de código, mas o resultado parece exatamente o mesmo:

[source, python]
----
>>> s1 = 'café'
>>> s2 = 'cafe\N{COMBINING ACUTE ACCENT}'
>>> s1, s2
('café', 'café')
>>> len(s1), len(s2)
(4, 5)
>>> s1 == s2
False
----

Colocar `COMBINING ACUTE ACCENT` (U+0301) após o "e" resulta em "é".
No padrão Unicode, sequências como `'é'` e `'e\u0301'` são chamadas de "equivalentes canônicas",
e se espera que as aplicações as tratem como iguais.
Mas Python vê duas sequências de pontos de código diferentes, e não as considera iguais.

A solução é a `unicodedata.normalize()`.
O primeiro argumento para essa função é uma dessas quatro strings: `'NFC'`, `'NFD'`, `'NFKC'`, e `'NFKD'`.
Vamos começar pelas duas primeiras.

A Forma Normal C (NFC)((("Normalization Form C (NFC)"))) combina os ponto de código para
produzir a string equivalente mais curta, enquanto a NFD decompõe,
expandindo os caracteres compostos em caracteres base e separando caracteres combinados.
Ambas as normalizações fazem as comparações funcionarem da forma esperada, como mostra o próximo exemplo:

[source, python]
----
>>> from unicodedata import normalize
>>> s1 = 'café'
>>> s2 = 'cafe\N{COMBINING ACUTE ACCENT}'
>>> len(s1), len(s2)
(4, 5)
>>> len(normalize('NFC', s1)), len(normalize('NFC', s2))
(4, 4)
>>> len(normalize('NFD', s1)), len(normalize('NFD', s2))
(5, 5)
>>> normalize('NFC', s1) == normalize('NFC', s2)
True
>>> normalize('NFD', s1) == normalize('NFD', s2)
True
----

Drivers de teclado normalmente geram caracteres compostos,
então o texto digitado pelos usuários usará NFC.
Entretanto, por segurança,
pode ser melhor normalizar as strings com `normalize('NFC', user_text)` antes de salvá-las.
A NFC também é a forma de normalização recomendada pelo W3C em
«_Character Model for the World Wide Web: String Matching and Searching_
(Modelo de Caracteres para a World Wide Web: Casamento de Strings e Busca)» [.small]#&#91;fpy.li/4-15&#93;#.


Alguns caracteres singulares são normalizados pela NFC em um outro caractere singular.
O símbolo para o ohm (Ω), a unidade de medida de resistência elétrica,
é normalizado para a letra grega ômega maiúscula.
Eles são visualmente idênticos, mas diferentes quando comparados,
então a normalização pode evitar surpresas:

[source, python]
----
>>> from unicodedata import normalize, name
>>> ohm = '\u2126'
>>> name(ohm)
'OHM SIGN'
>>> ohm_c = normalize('NFC', ohm)
>>> name(ohm_c)
'GREEK CAPITAL LETTER OMEGA'
>>> ohm == ohm_c
False
>>> normalize('NFC', ohm) == normalize('NFC', ohm_c)
True
----

As outras duas formas de normalização são a NFKC e a NFKD, a letra K significando "compatibilidade".
Essas são formas mais fortes de normalizaçào, afetando os assim chamados "caracteres de compatibilidade".
Um dos objetivos do Unicode é oferecer um único ponto de código "canônico" para cada caractere.
Mas alguns caracteres aparecem mais de uma vez, por compatibilidade com codificações legadas.
Por exemplo, o `µ` (`MICRO SIGN`, `U+00B5`),
foi incluído para facilitar a conversão bi-direcional com o `latin1`, que o inclui,
apesar do mesmo caractere ser parte do alfabeto grego com o ponto de código `U+03BC` (`GREEK SMALL LETTER MU`).
Assim, o símbolo de micro é considerado um "caractere de compatibilidade".

Nas formas NFKC e NFKD, cada caractere de compatibilidade é substituído por uma "decomposição de compatibilidade"
de um ou mais caracteres, que é considerada a representação "preferencial",
mesmo se ocorrer alguma perda de formatação—idealmente,
a formatação deveria ser responsabilidade de alguma marcação externa, não parte do Unicode.
Para exemplificar, a decomposição de compatibilidade da fração um meio, `'½'` (`U+00BD`),
é a sequência de três caracteres `'1/2'`, e a decomposição de compatibilidade do símbolo de micro,
`'µ'` (`U+00B5`), é o mu minúsculo, `'μ'` (`U+03BC`).footnote:[Curiosamente,
o símbolo de micro é considerado um "caractere de compatibilidade",
mas o símbolo de ohm não.
O resultado disso é que a NFC não toca no símbolo de micro,
mas muda o símbolo de ohm para ômega maiúsculo,
ao passo que a NFKC e a NFKD mudam tanto o ohm quanto o micro para caracteres gregos.]

É assim que a NFKC funciona na prática:

[source, python]
----
>>> from unicodedata import normalize, name
>>> half = '\N{VULGAR FRACTION ONE HALF}'
>>> print(half)
½
>>> normalize('NFKC', half)
'1/2'
>>> for char in normalize('NFKC', half):
...     print(char, name(char), sep='\t')
...
1	DIGIT ONE
/	FRACTION SLASH
2	DIGIT TWO
>>> four_squared = '4²'
>>> normalize('NFKC', four_squared)
'42'
>>> micro = 'µ'
>>> micro_kc = normalize('NFKC', micro)
>>> micro, micro_kc
('µ', 'μ')
>>> ord(micro), ord(micro_kc)
(181, 956)
>>> name(micro), name(micro_kc)
('MICRO SIGN', 'GREEK SMALL LETTER MU')
----

Ainda que `'1/2'` seja um substituto razoável para `'½'`,
e o símbolo de micro ser realmente a letra grega mu minúscula, converter `'4²'` para `'42'` muda o sentido.
Uma aplicação poderia armazenar `'4²'` como `'4<sup>2</sup>'`,
mas a função `normalize` não sabe nada sobre formatação.
Assim, NFKC ou NFKD podem perder ou distorcer informações,
mas podem produzir representações intermediárias convenientes para buscas ou indexação.

Infelizmente, com o Unicode tudo é sempre mais complicado do que parece à primeira vista.
Para o `VULGAR FRACTION ONE HALF`, a normalização NFKC produz 1 e 2 unidos pelo `FRACTION SLASH`
em vez do `SOLIDUS`, também conhecido como "barra" ("slash" em inglês)—o familiar
caractere com código decimal 47 na tabela ASCII.
Portanto, buscar pela sequência ASCII de três caracteres `'1/2'`
não encontraria a sequência Unicode normalizada.footnote:[Neste trecho, apenas no livro impresso,
troquei o caractere `FRACTION SLASH` pelo `SOLIDUS` porque o primeiro estava aparecendo como tofu,
e ambos têm a mesma aparência. O código no HTML está fiel ao que eu queria mostrar.]

[WARNING]
====
As normalizações NFKC e NFKD causam perda de dados e devem ser aplicadas apenas em casos especiais,
como busca e indexação, e não para armazenamento permanente do texto.
====

Ao preparar texto para indexação, há outra operação útil: 
_case folding_ footnote:[NT: algo como "dobra" ou "mudança" de caixa.], nosso próximo assunto.


==== Case folding

_Case folding_((("case folding"))) é essencialmente a conversão de todo o texto para minúsculas,
com algumas transformações adicionais.
A operação é suportada pelo método `str.casefold()`.

Para qualquer string `s` contendo apenas caracteres `latin1`, `s.casefold()`
produz o mesmo resultado de `s.lower()`, com apenas duas exceções—o símbolo de micro, `'µ'`,
é trocado pela letra grega mu minúscula (que é exatamente igual na maioria das fontes)
e a letra alemã _Eszett_ (ß), também chamada "s agudo" (_scharfes S_), se torna "ss":

<<<
[source, python]
----
>>> micro = 'µ'
>>> name(micro)
'MICRO SIGN'
>>> micro_cf = micro.casefold()
>>> name(micro_cf)
'GREEK SMALL LETTER MU'
>>> micro, micro_cf
('µ', 'μ')
>>> eszett = 'ß'
>>> name(eszett)
'LATIN SMALL LETTER SHARP S'
>>> eszett_cf = eszett.casefold()
>>> eszett, eszett_cf
('ß', 'ss')
----

Há quase 300 pontos de código para os quais `str.casefold()` e `str.lower()` devolvem resultados diferentes.

Como acontece com qualquer coisa relacionada ao Unicode, _case folding_ é um tópico complexo,
com muitos casos linguísticos especiais, mas os desenvolvedores de Python fizeram
um grande esforço para apresentar uma solução que, espera-se, funcione para a maioria dos usuários.

Nas próximas seções vamos colocar nosso conhecimento sobre normalização para trabalhar,
desenvolvendo algumas funções utilitárias.


==== Funções utilitárias para casamento de texto normalizado

Como((("normalized text matching", id="normtext04"))) vimos, é seguro usar a NFC e a NFD,
e ambas permitem comparações razoáveis entre strings Unicode.
A NFC é a melhor forma normalizada para a maioria das aplicações,
e `str.casefold()` é a opção certa para comparações indiferentes a maiúsculas/minúsculas.

Se você precisa lidar com texto em muitas línguas diferentes,
seria muito útil acrescentar às suas ferramentas de trabalho um par de funções como `nfc_equal` e `fold_equal`,
do <<ex_normeq>>.

[[ex_normeq]]
.normeq.py: comparação de strings Unicode normalizadas
====
[source, python]
----
include::../code/04-text-byte/normeq.py[]
----
====

Além da normalização e do _case folding_ do Unicode—ambos partes desse padrão—algumas
vezes faz sentido aplicar transformações mais profundas,
como, por exemplo, mudar `'café'` para `'cafe'`.
Vamos ver quando e como na próxima seção.

==== "Normalização" extrema: removendo sinais diacríticos

O((("diacritics, normalization and", id="diacritics04"))) tempero secreto da busca do Google inclui muitos truques,
mas um deles aparentemente é ignorar sinais diacríticos (acentos e cedilhas, por exemplo),
pelo menos em alguns contextos.
Remover sinais diacríticos não é uma forma regular de normalização,
pois muitas vezes muda o sentido das palavras e pode produzir falsos positivos em uma busca.
Mas ajuda a lidar com alguns fatos da vida:
as pessoas às vezes são preguiçosas ou desconhecem o uso correto dos sinais diacríticos,
e regras de ortografia mudam com o tempo,
levando acentos a desaparecerem e reaparecerem nas línguas vivas.

Além do caso da busca, eliminar os acentos torna as URLs mais legíveis,
pelo menos nas línguas latinas.
Veja a URL do artigo da Wikipedia sobre a cidade de São Paulo:

----
https://en.wikipedia.org/wiki/S%C3%A3o_Paulo
----

O trecho `%C3%A3` é a representação em UTF-8 de uma única letra,
o "ã" ("a" com til). A forma a seguir é mais fácil de reconhecer, mesmo com a ortografia incorreta:

----
https://en.wikipedia.org/wiki/Sao_Paulo
----

Para remover todos os sinais diacríticos de uma `str`, você pode usar uma função como a do <<ex_shave_marks>>.

<<<
[[ex_shave_marks]]
.simplify.py: função para remover todas as marcações combinadas
====
[source, python]
----
include::../code/04-text-byte/simplify.py[tags=SHAVE_MARKS]
----
====
<1> Decompõe todos os caracteres em caracteres base e marcações combinadas.
<2> Filtra e retira todas as marcações combinadas.
<3> Recompõe todos os caracteres.


<<ex_shave_marks_demo>> mostra alguns usos para `shave_marks`.

[[ex_shave_marks_demo]]
.Dois exemplos de uso da `shave_marks` do <<ex_shave_marks>>
====
[source, python]
----
>>> order = '“Herr Voß: • ½ cup of Œtker™ caffè latte • bowl of açaí.”'
>>> shave_marks(order)
'“Herr Voß: • ½ cup of Œtker™ caffe latte • bowl of acai.”'  <1>
>>> Greek = 'Ζέφυρος, Zéfiro'
>>> shave_marks(Greek)
'Ζεφυρος, Zefiro'  <2>
----
====
<1> Apenas as letras "è", "ç", e "í" foram substituídas.footnote:[Perdão pelos tofu neste exemplo,
são aspas duplas assimétricas e dois quadradinhos preto. 
O exemplo está correto «PythonFluente.com» [.small]#&#91;fpy.li/5v&#93;#.]
<2> Tanto "έ" quando "é" foram substituídas.

A função `shave_marks` do <<ex_shave_marks>> funciona bem, mas talvez vá longe demais.
Frequentemente, a razão para remover os sinais diacríticos é transformar texto de
uma língua latina para ASCII puro, mas `shave_marks` também troca caracteres
não-latinos–como letras gregas–que nunca se tornarão ASCII apenas pela remoção de acentos.
Então faz sentido analisar cada caractere base e remover as marcações anexas
apenas se o caractere base for uma letra do alfabeto latino. É isso que o <<ex_shave_marks_latin>> faz.

[[ex_shave_marks_latin]]
.Função para remover marcações combinadas de caracteres latinos (comando de importação omitidos, pois isso é parte do módulo simplify.py do <<ex_shave_marks>>)
====
[source, python]
----
include::../code/04-text-byte/simplify.py[tags=SHAVE_MARKS_LATIN]
----
====
<1> Decompõe todos os caracteres em caracteres base e marcações combinadas.
<2> Pula as marcações combinadas quando o caractere base é latino.
<3> Caso contrário, mantém o caractere original.
<4> Detecta um novo caractere base e determina se ele é latino.
<5> Recompõe todos os caracteres.

Um passo ainda mais radical substituiria os símbolos comuns em textos de línguas européias
(por exemplo, aspas curvas, travessões, os círculos de _bullet points_, etc)
em seus equivalentes `ASCII`. É isso que a função `asciize` faz no <<ex_asciize>>.

<<<
[[ex_asciize]]
.Transforma alguns símbolos tipográficos européias em ASCII (este trecho também é parte do simplify.py do <<ex_shave_marks>>)
====
[source, python]
----
include::../code/04-text-byte/simplify.py[tags=ASCIIZE]
----
====
<1> Cria tabela de mapeamento para substituição de caracteres; veja
este exemplo sem tofu no «PythonFluente.com» [.small]#&#91;fpy.li/5w&#93;#.
<2> Tabela de mapeamento para trocar certos caracteres por strings.
<3> Funde as tabelas de mapeamento.
<4> `dewinize` não afeta texto em `ASCII` ou `latin1`, apenas os acréscimos da Microsoft ao `latin1` no `cp1252`.
<5> Aplica `dewinize` e remove as marcações de sinais diacríticos.
<6> Substitui o _Eszett_ por "ss" (não estamos usando _case folding_ aqui, pois queremos preservar maiúsculas e minúsculas).
<7> Aplica a normalização NFKC para compor os caracteres com seus pontos de código de compatibilidade.

O <<ex_asciize_demo>> mostra a `asciize` em ação.

[[ex_asciize_demo]]
.Dois exemplos usando `asciize`, do <<ex_asciize>>
====
[source, python]
----
>>> order = '“Herr Voß: • ½ cup of Œtker™ caffè latte • bowl of açaí.”'
>>> dewinize(order)
'"Herr Voß: - ½ cup of OEtker(TM) caffè latte - bowl of açaí."'  <1>
>>> asciize(order)
'"Herr Voss: - 1/2 cup of OEtker(TM) caffe latte - bowl of acai."'  <2>
----
====
<1> `dewinize` substitui as aspas curvas, os _bullets_, e o ™ (símbolo de marca registrada).
<2> `asciize` aplica `dewinize`, remove os sinais diacríticos e substitui o `'ß'`.

[WARNING]
====
Cada idioma tem suas próprias regras para remoção de sinais diacríticos.
Por exemplo, os alemães trocam o `'ü'` por `'ue'`. Nossa função `asciize` não é tão refinada,
então pode ou não ser adequada para seu idioma.
Mas é aceitável para o português.
====

Resumindo, as funções em _simplify.py_ vão bem além da normalização padrão,
e realizam uma cirurgia profunda no texto, com boas chances de mudar seu sentido.
Só você pode decidir se deve ir tão longe, conhecendo o idioma alvo,
os seus usuários e a forma como o texto transformado será utilizado.

Vamos agora ordenar nosso entendimento sobre ordenação no
Unicode.((("", startref="UTVnormal04")))((("", startref="Snormal04")))((("",
startref="normtext04")))((("", startref="diacritics04")))


[[sorting_unicode_sec]]
=== Ordenando texto Unicode

Python((("Unicode text versus bytes", "sorting Unicode text", id="UTVsort04")))
ordena sequências de qualquer tipo comparando um por um os itens em cada sequência.
Para strings, isso significa comparar pontos de código.
Infelizmente, isso produz resultados inaceitáveis para qualquer um que use caracteres não-ASCII.

Considere ordenar uma lista de frutas cultivadas no Brasil:

[source, python]
----
>>> fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola']
>>> sorted(fruits)
['acerola', 'atemoia', 'açaí', 'caju', 'cajá']
----

As regras de ordenação variam entre diferentes locales,
mas em português e em muitas línguas que usam o alfabeto latino,
acentos e cedilhas raramente fazem diferença na ordenação.footnote:[Sinais
diacríticos afetam a ordenação apenas nos raros casos em que eles são
a única diferença entre duas palavras—nesse caso,
a palavra com o sinal diacrítico é colocada após a palavra sem o sinal na ordenação.]
Então "cajá" é lido como "caja" e deve vir antes de "caju".

A lista `fruits` ordenada deveria ser:

[source, python]
----
['açaí', 'acerola', 'atemoia', 'cajá', 'caju']
----

O modo padrão de ordenar texto não-ASCII em Python é usar a função `locale.strxfrm` que, de acordo com a
https://fpy.li/3e[documentação do
módulo `locale`] "transforma uma string em uma que pode ser usada em comparações com reconhecimento de localidade."

Para poder usar `locale.strxfrm`, você deve primeiro definir um _locale_ adequado para sua aplicação,
e rezar para que o SO o suporte.
A sequência de comando no <<ex_locale_sort>> pode funcionar para você.

[[ex_locale_sort]]
._locale_sort.py_: Usando a função `locale.strxfrm` como chave de ordenamento
====
[source, python]
----
include::../code/04-text-byte/locale_sort.py[]
----
====

Executando o <<ex_locale_sort>> no GNU/Linux (Ubuntu 19.10)
com o _locale_ `pt_BR.UTF-8` instalado, consigo o resultado correto:

[source, python]
----

'pt_BR.UTF-8'
['açaí', 'acerola', 'atemoia', 'cajá', 'caju']
----

Portanto, você precisa chamar `setlocale(LC_COLLATE, locale_desejado)`
antes de usar `locale.strxfrm` como a chave de ordenação.

Porém, aqui vão algumas ressalvas:

* Como as configurações de _locale_ são globais, não é recomendado chamar `setlocale` em uma biblioteca.
Sua aplicação ou framework deveria definir o _locale_ no início do processo, e não o mudar mais depois disso.
* O _locale_ desejado deve estar instalado no SO,
caso contrário `setlocale` gera uma exceção de `locale.Error: unsupported locale setting`.
* Você precisa saber como escrever corretamente o nome do locale.
* O _locale_ precisa ser corretamente implementado pelos desenvolvedores do SO.
Tive sucesso com o Ubuntu 19.10, mas não no macOS 10.14.
No macOS, a chamada `setlocale(LC_COLLATE, 'pt_BR.UTF-8')` devolve a string `'pt_BR.UTF-8'` sem qualquer reclamação.
Mas `sorted(fruits, key=locale.strxfrm)` produz o mesmo resultado incorreto de  `sorted(fruits)`.
Também tentei os locales `fr_FR`, `es_ES`, e `de_DE` no macOS,
mas `locale.strxfrm` nunca fez seu trabalho direito.footnote:[De novo,
eu não consegui encontrar uma solução, mas encontrei outras pessoas relatando o mesmo problema.
Alex Martelli, um dos revisores técnicos, não teve problemas para usar `setlocale` e `locale.strxfrm`
em seu Mac com o macOS 10.9. Em resumo: cada caso é um caso.]

Portanto, a solução da biblioteca padrão para ordenação internacionalizada funciona,
mas parece ter suporte adequado apenas no GNU/Linux
(talvez também no Windows, se você for um especialista).
Mesmo assim, ela depende das configurações do _locale_, criando dores de cabeça na implantação.

Felizmente, há uma solução mais simples: a((("pyuca library"))) biblioteca _pyuca_, disponível no _PyPI_.

==== Ordenando com o Algoritmo de Ordenação do Unicode

James Tauber, contribuidor((("Unicode Collation Algorithm (UCA)"))) muito ativo do Django,
deve ter sentido as dores de lidar com _locale_. Ele criou a
«_pyuca_» [.small]#&#91;fpy.li/4-17&#93;#,
uma implementação integralmente em Python do Algoritmo de Ordenação do Unicode
(UCA, sigla em inglês para _Unicode Collation Algorithm_).
O <<ex_pyuca_sort>> mostra como ela é fácil de usar.

[[ex_pyuca_sort]]
.Utilizando o método `pyuca.Collator.sort_key`
====
[source, python]
----
>>> import pyuca
>>> coll = pyuca.Collator()
>>> fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola']
>>> sorted_fruits = sorted(fruits, key=coll.sort_key)
>>> sorted_fruits
['açaí', 'acerola', 'atemoia', 'cajá', 'caju']
----
====

Isso é simples e funciona no GNU/Linux, no macOS, e no Windows, pelo menos com a minha pequena amostra.

A `pyuca` não leva o _locale_ em consideração.
Se você precisar customizar a ordenação,
pode fornecer um caminho para uma tabela própria de ordenação para o construtor `Collator()`.
Sem qualquer configuração adicional, a biblioteca usa o «_allkeys.txt_» [.small]#&#91;fpy.li/4-18&#93;#, incluído no projeto.
Esse arquivo é apenas uma cópia da
«_Default Unicode Collation Element Table_ (Tabela Default de Ordenação de Elementos Unicode)»
[.small]#&#91;fpy.li/4-19&#93;# do _Unicode.org_.

.PyICU: A recomendação do Miro para ordenação com Unicode
[TIP]
====
(O revisor técnico Miroslav Šedivý é um poliglota e um especialista em Unicode.
Eis o que ele escreveu sobre a _pyuca_.)

A _pyuca_((("PyICU"))) tem um algoritmo de ordenação que não respeita o padrão de ordenação de linguagens individuais.
Por exemplo, [a letra] Ä em alemão fica entre o A e o B, enquanto em sueco ela vem depois do Z.
Dê uma olhada na «_PyICU_» [.small]#&#91;fpy.li/4-20&#93;#,
que funciona sem modificar o locale do processo.
Ela também é necessária se você quiser mudar a capitalização de iİ/ıI em turco.
A PyICU inclui uma extensão que precisa ser compilada,
então pode ser mais difícil de instalar em alguns sistemas que a _pyuca_,
que é toda feita em Python.
====

E por sinal, aquela tabela de ordenação é um dos muitos arquivos de dados que formam o banco de dados do Unicode,
nosso próximo assunto.((("", startref="UTVsort04")))

[[unicodedata_sec]]
=== O banco de dados do Unicode

O((("Unicode text versus bytes", "Unicode database", id="UTVdatabase04"))) padrão Unicode
fornece todo um banco de dados—na forma de vários arquivos de texto estruturados—que inclui
não apenas a tabela mapeando pontos de código para nomes de caracteres,
mas também metadados sobre os caracteres individuais e como eles se relacionam.
Por exemplo, o banco de dados do Unicode registra se um caractere pode ser impresso,
se é uma letra, um dígito decimal ou algum outro símbolo numérico.
É assim que os métodos de `str` `isalpha`, `isprintable`, `isdecimal` e `isnumeric` funcionam.
`str.casefold` também usa informação de uma tabela do Unicode.

[NOTE]
====
A função `unicodedata.category(char)` devolve uma categoria de `char` com duas letras,
do banco de dados do Unicode.
Os métodos de alto nível de `str` são mais fáceis de usar.
Por exemplo,
«`label.isalpha()`» [.small]#&#91;fpy.li/3f&#93;#
devolve `True` se todos os caracteres em `label`
pertencerem a uma das seguintes categorias: `Lm`, `Lt`, `Lu`, `Ll`, or `Lo`.
Para descobrir o que esses códigos significam, veja
«_General Category_» [.small]#&#91;fpy.li/4-22&#93;#  no artigo «_Unicode character property_» [.small]#&#91;fpy.li/4-23&#93;#
(EN) da Wikipedia em inglês.
====

[[finding_chars_sec]]
==== Encontrando caracteres por nome

O((("emojis", "finding characters by name", id="Efind04")))((("characters", "finding Unicode by name",
id="Cfinduni04"))) módulo `unicodedata` tem funções para obter os metadados de caracteres, incluindo
`unicodedata.name()`, que devolve o nome oficial do caractere no padrão.
A <<unicodedata_name_fig>> demonstra essa função.footnote:[Aquilo é uma
imagem—não uma listagem de código—porque, no momento em que esse capítulo foi escrito,
os emojis não têm um bom suporte no sistema de publicação digital da O'Reilly.]

[[unicodedata_name_fig]]
.Explorando `unicodedata.name()` no console de Python.
image::../images/flpy_0405.png[]

Você pode usar a função `name()` para criar aplicações que permitem aos usuários buscarem caracteres por nome.
A <<cf_demo_fig>> demonstra o script de linha de comando _cf.py_,
que recebe como argumentos uma ou mais palavras,
e lista os caracteres que tem aquelas palavras em seus nomes Unicode oficiais.
O código-fonte completo de _cf.py_ aparece no <<ex_cfpy>>.

[[cf_demo_fig]]
.Usando _cf.py_ para encontrar gatos sorridentes.
image::../images/flpy_0406.png[]

[WARNING]
====
O((("emojis", "varied support for"))) suporte a emojis varia muito entre sistemas operacionais e aplicativos.
Nos últimos anos, o terminal do macOS tem oferecido o melhor suporte para emojis,
seguido por terminais gráficos GNU/Linux modernos.
O _cmd.exe_ e o PowerShell do Windows agora suportam saída Unicode,
mas quando escrevi essa seção, em janeiro de 2020,
eles não mostravam emojis por padrão.
O revisor técnico Leonardo Rochael me falou sobre um novo «terminal para Windows da Microsoft» [.small]#&#91;fpy.li/4-24&#93;#, de código aberto,
que pode ter um suporte melhor a Unicode que os consoles antigos da Microsoft.
Ainda não testei.
====

No <<ex_cfpy>>, observe que o comando `if`, na função `find`,
usa o método `.issubset()` para testar rapidamente se
todas as palavras no conjunto `query` aparecem na lista de palavras criada a partir do nome do caractere.
Graças à rica API de conjuntos de Python,
não precisamos de um loop `for` aninhado e de outro `if` para implementar essa verificação

[[ex_cfpy]]
.cf.py: o utilitário de busca de caracteres
====
[source, python]
----
include::../code/04-text-byte/charfinder/cf.py[]
----
====
<1> Configura os defaults para a faixa de pontos de código da busca.
<2> `find` aceita `query_words` e somente argumentos nomeados (opcionais) para limitar a faixa da busca, facilitando os testes.
<3> Converte `query_words` em um conjunto de strings capitalizadas.
<4> Obtém o caractere Unicode para `code`.
<5> Obtém o nome do caractere, ou `None` se o ponto de código não estiver atribuído a um caractere.
<6> Se há um nome, separa esse nome em uma lista de palavras, então verifica se o conjunto `query` é um subconjunto daquela lista.
<7> Mostra uma linha com o ponto de código no formato `U+9999`, o caractere e seu nome.

O módulo `unicodedata` tem outras funções interessantes.
A seguir veremos algumas delas, relacionadas a obter informação de caracteres com
significado numérico.((("", startref="Efind04")))((("", startref="Cfinduni04")))


==== O sentido numérico de caracteres

O((("characters", "numeric meaning of", id="Cnumeric04"))) módulo `unicodedata`
inclui funções para determinar se um caractere Unicode representa um número e,
se for esse o caso, seu valor numérico em termos humanos—em contraste com o número de seu ponto de código.

O <<ex_numerics_demo>> demonstra o uso de `unicodedata.name()` e `unicodedata.numeric()`,
junto com os métodos `.isdecimal()` e `.isnumeric()` de `str`.

[[ex_numerics_demo]]
.Demo do banco de dados Unicode de metadados de caracteres numéricos
(as notas explicativas descrevem cada coluna da saída)
====
[source, python]
----
include::../code/04-text-byte/numerics_demo.py[tags=NUMERICS_DEMO]
----
====
<1> Ponto de código no formato `U+0000`.
<2> O caractere, centralizado em uma `str` de tamanho 6.
<3> Mostra `re_dig` se o caractere casa com a regex `r'\d'`.
<4> Mostra `isdig` se `char.isdigit()` é `True`.
<5> Mostra `isnum` se `char.isnumeric()` é `True`.
<6> Valor numérico formatado com tamanho 5 e duas casa decimais.
<7> O nome Unicode do caractere.

Executar o <<ex_numerics_demo>> gera a <<numerics_demo_fig>>,
se a fonte do seu terminal incluir todos aqueles símbolos.

[[numerics_demo_fig]]
.Saída do terminal do macOS com caracteres numéricos e alguns metadados; `re_dig` significa que o caractere casa com a expressão regular `r'\d'`.iid
image::../images/flpy_0407.png[Captura de tela de caracteres numéricos]

A sexta coluna da <<numerics_demo_fig>> é o resultado da chamada a `unicodedata.numeric(char)` com o caractere.
Ela mostra que o Unicode sabe o valor numérico de símbolos que representam números.
Assim, se você quiser criar uma aplicação de planilha que suporta dígitos tamil ou numerais romanos, vá fundo!

A <<numerics_demo_fig>> mostra que a expressão regular `r'\d'` casa com o dígito "1" e com o dígito 3 da escrita devanágari,
mas não com alguns outros caracteres considerados dígitos pela função `isdigit`.
O módulo `re` não é tão conhecedor de Unicode quanto deveria ser.
O novo módulo `regex`, disponível no PyPI,
foi projetado para um dia substituir o `re`,
e fornece um suporte melhor ao Unicode.footnote:[Embora
não tenha se saído melhor que o `re` para identificar dígitos nessa amostra em particular.]
Voltaremos ao módulo `re` na próxima seção.

Ao longo desse capítulo, usamos várias funções de `unicodedata`,
mas há muitas outras que não mencionamos.
Veja a documentação da biblioteca padrão para o «módulo `unicodedata`» [.small]#&#91;fpy.li/3g&#93;#.

A seguir vamos dar uma rápida passada pelas APIs de modo dual,
com funções que aceitam argumentos `str` ou `bytes` e dão a eles tratamento especial
dependendo do tipo.((("", startref="Cnumeric04")))((("", startref="UTVdatabase04")))

<<<
[[dual_mode_api_sec]]
=== APIs de modo dual para str e bytes

A biblioteca padrão de Python((("Unicode text versus bytes", "dual-mode str and bytes APIs",
id="UTVdual04")))((("strings", "dual-mode str and bytes APIs", id="Sdual04")))
tem funções que aceitam argumentos `str` ou `bytes` e se comportam de forma diferente dependendo do tipo recebido.
Alguns exemplos podem ser encontrados nos módulos `re` e `os`.

==== str versus bytes em expressões regulares

Se((("regular expressions, str versus bytes in"))) você criar uma expressão regular com `bytes`,
padrões tal como `\d` e `\w` vão casar apenas com caracteres ASCII;
por outro lado, se esses padrões forem passados como `str`,
eles vão casar com dígitos Unicode ou letras além do ASCII.
O <<ex_re_demo>> e a <<fig_re_demo>> comparam como letras,
dígitos ASCII, superescritos e dígitos tamil casam em padrões `str` e `bytes`.

[[ex_re_demo]]
.ramanujan.py: compara o comportamento de expressões regulares simples como `str` e como `bytes`
====
[source, python]
----
include::../code/04-text-byte/ramanujan.py[tags=RE_DEMO]
----
====
<1> As duas primeiras expressões regulares são do tipo `str`.
<2> As duas últimas são do tipo `bytes`.
<3> Texto Unicode para ser usado na busca, contendo os dígitos tamil para `1729`
(a linha lógica continua até o símbolo de fechamento de parênteses).
<4> Essa string é unida à anterior no momento da compilação
(veja https://fpy.li/3h["2.4.2. String literal concatenation"
(_Concatenação de strings literais_)] em _A Referência da Linguagem Python_).
<5> Uma string `bytes` é necessária para a busca com as expressões regulares `bytes`.
<6> O padrão `str` `r'\d+'` casa com os dígitos ASCII e tamil.
<7> O padrão `bytes` `rb'\d+'` casa apenas com os bytes ASCII para dígitos.
<8> O padrão `str` `r'\w+'` casa com letras, superescritos e dígitos tamil e ASCII.
<9> O padrão `bytes` `rb'\w+'` casa apenas com bytes ASCII para letras e dígitos.

[[fig_re_demo]]
.Captura de tela da execução de ramanujan.py do <<ex_re_demo>>.
image::../images/flpy_0408.png[Saída de ramanujan.py]

O <<ex_re_demo>> é um exemplo trivial para destacar um ponto:
você pode usar expressões regulares com `str` ou `bytes`,
mas nesse último caso os bytes fora da faixa do ASCII são tratados como caracteres que não representam dígitos nem palavras.

Para expressões regulares `str`, há uma marcação `re.ASCII`,
que faz `\w`, `\W`, `\b`, `\B`, `\d`, `\D`, `\s`, e `\S`
casarem apenas com ASCII.
Veja a
«documentação do módulo `re`» [.small]#&#91;fpy.li/3j&#93;# para mais detalhes.

Outro módulo importante é o `os`.

==== str versus bytes nas funções de os

O((("os functions, str versus bytes in"))) kernel do GNU/Linux não conhece Unicode então, no mundo real,
você pode encontrar nomes de arquivo compostos de sequências de bytes que
não são válidas em nenhum esquema razoável de codificação, e não podem ser decodificados para `str`.
Esta é uma situação comum em servidores de arquivo com clientes usando uma variedade de diferentes
SO.

Para mitigar esse problema, todas as funções do módulo `os` que
aceitam nomes de arquivo ou caminhos podem receber seus argumentos como `str` ou `bytes`.
Se uma dessas funções é chamada com um argumento `str`,
o argumento será automaticamente convertido usando o codec informado por
`sys.getfilesystemencoding()`,
e a resposta do SO será decodificada com o mesmo codec.
Isso é quase sempre o que se deseja, mantendo a melhor prática do sanduíche de Unicode.

Mas se você precisa lidar com (e provavelmente corrigir) nomes de arquivo que
não podem ser processados daquela forma,
você pode passar argumentos `bytes` para as funções de `os`, e receber `bytes` de volta.
Esse recurso permite que você processe qualquer nome de arquivo ou caminho,
independente de quantos gremlins encontrar.
Veja o <<ex_listdir1>>.

[[ex_listdir1]]
.`listdir` com argumentos `str` e `bytes`, e os resultados
====
[source, python]
----
>>> os.listdir('.')  # <1>
['abc.txt', 'digits-of-π.txt']
>>> os.listdir(b'.')  # <2>
[b'abc.txt', b'digits-of-\xcf\x80.txt']
----
====
<1> O segundo nome de arquivo é "digits-of-π.txt" (com a letra grega pi).
<2> Dado um argumento `byte`, `listdir` devolve nomes de arquivos como bytes:
`b'\xcf\x80'` é a codificação UTF-8 para a letra grega pi.

Para ajudar no processamento manual de sequências `str` ou `bytes` que são nomes de arquivos ou caminhos,
o módulo `os` fornece funções especiais de codificação e decodificação,
`os.fsencode(name_or_path)` e `os.fsdecode(name_or_path)`.
Ambas as funções aceitam argumentos dos tipos `str`, `bytes` ou,
desde o Python 3.6, um objeto que implemente a interface `os.PathLike`.

O Unicode é um buraco de coelho bem fundo.
É hora de encerrar nossa exploração de `str` e `bytes`.((("", startref="UTVdual04")))((("", startref="Sdual04")))


=== Resumo do capítulo

Começamos((("Unicode text versus bytes", "overview of")))
o capítulo descartando a noção de que `1 caractere == 1 byte`.
A medida que o mundo adota o Unicode,
precisamos manter o conceito de strings de texto separado das
sequências binárias que as representam em arquivos, e Python 3 reforça essa separação.

Após uma breve passada pelos tipos de dados sequências
binárias—`bytes`, `bytearray`, e `memoryview`—mergulhamos na codificação e na decodificação,
com uma amostragem dos codecs importantes,
seguida por abordagens para prevenir ou lidar com os abomináveis
`UnicodeEncodeError`, `UnicodeDecodeError` e os `SyntaxError`
causados pela codificação errada em arquivos de código-fonte de Python.

A seguir consideramos a teoria e a prática de detecção de codificação na ausência de metadados:
em teoria, não pode ser feita, mas, na prática, o pacote Chardet
consegue realizar esse feito para uma grande quantidade de codificações populares.
Marcadores de ordem de bytes foram apresentados como a única dica de codificação
encontrada em arquivos UTF-16 e UTF-32–algumas vezes também em arquivos UTF-8.

Na seção seguinte, demonstramos como abrir arquivos de texto, uma tarefa fácil exceto por uma armadilha:
o argumento nomeado `encoding=` não é obrigatório quando se abre um arquivo de texto, mas deveria ser.
Se você não especificar a codificação, terminará com um programa que consegue produzir "texto puro" que
é incompatível entre diferentes plataformas, devido a codificações default conflitantes.
Expusemos então as diferentes configurações de codificação usadas pelo Python, e como detectá-las.

Uma triste realidade para usuários de Windows é o fato dessas configurações
muitas vezes terem valores diferentes dentro da mesma máquina,
e desses valores serem mutuamente incompatíveis;
usuários do GNU/Linux e do macOS, por outro lado, vivem em um lugar mais feliz,
onde o UTF-8 é o default por (quase) toda parte.

O Unicode fornece múltiplas formas de representar alguns caracteres,
então a normalização é um pré-requisito para a comparação de textos.
Além de explicar a normalização e o _case folding_,
apresentamos algumas funções úteis que podem ser adaptadas para as suas necessidades,
incluindo transformações drásticas como a remoção de todos os acentos.
Vimos como ordenar corretamente texto Unicode,
usando o módulo padrão `locale`—com algumas restrições—e
uma alternativa que não depende de complexas configurações de locale:
a biblioteca((("pyuca library"))) externa _pyuca_.

Usamos o banco de dados do Unicode para programar um utilitário de linha de comando que
busca caracteres por nome–em 28 linhas de código, graças ao poder de Python.
Demos uma olhada em outros metadados do Unicode, e vimos rapidamente as APIs de modo dual,
onde algumas funções podem ser chamadas com argumentos `str` ou `bytes`, produzindo resultados diferentes.


=== Para saber mais

A palestra((("Unicode text versus bytes", "further reading on"))) de Ned Batchelder na PyCon US 2012,
«_Pragmatic Unicode_» [.small]#&#91;fpy.li/4-28&#93;# foi marcante.
Ned é tão profissional que publicou uma transcrição completa da palestra, além de slides e vídeo.

_Character encoding and Unicode in Python_ (Codificação de caracteres e o Unicode no Python)
(«slides» [.small]#&#91;fpy.li/4-1&#93;#, «vídeo» [.small]#&#91;fpy.li/4-2&#93;#)
foi uma excelente palestra de Esther Nam e Travis Fischer na PyCon 2014,
e foi onde encontrei a concisa epígrafe desse capítulo:
"Humanos usam texto. Computadores falam em bytes."

Lennart Regebro—um dos revisores técnicos da primeira edição deste livro—compartilha seu
_Useful Mental Model of Unicode (UMMU)_ (Modelo Mental Útil do Unicode) em um post curto,
«_Unconfusing Unicode: What Is Unicode?_ (Desconfundindo o Unicode: o que é o Unicode?)» [.small]#&#91;fpy.li/4-31&#93;#.
O Unicode é um padrão complexo, então o UMMU de Lennart é um ponto de partida muito útil.

O «_Unicode HOWTO_» [.small]#&#91;fpy.li/3k&#93;#
oficial na documentação de Python aborda o assunto por vários ângulos diferentes,
de uma boa introdução histórica a detalhes de sintaxe, codecs, expressões regulares, nomes de arquivo,
e boas práticas para E/S sensível ao Unicode (isto é, o sanduíche de Unicode),
com vários links adicionais de referências em cada seção.

O «Capítulo 4, "Strings"» [.small]#&#91;fpy.li/4-33&#93;#,
do excelente livro «_Dive into Python 3_» [.small]#&#91;fpy.li/4-34&#93;# 
de Mark Pilgrim (Apress), também fornece uma ótima introdução ao suporte a Unicode no Python 3.
No mesmo livro, o «Capítulo 15» [.small]#&#91;fpy.li/4-35&#93;# descreve
como a biblioteca Chardet foi portada de Python 2 para Python 3,
um valioso estudo de caso, dado que a mudança do antigo tipo `str` para o novo `bytes`
é a causa da maioria das dores da migração,
e esta é uma preocupação central em uma biblioteca projetada para detectar codificações.

Se você conhece Python 2, mas é novo no Python 3, o artigo
«_What's New in Python 3.0_ (O quê há de novo no Python 3.0)» [.small]#&#91;fpy.li/4-36&#93;#,
de Guido van Rossum, tem 15 pontos resumindo as mudanças, com vários links.
Guido começa com uma afirmação brutal:
"Tudo que você achava que sabia sobre dados binários e Unicode mudou."
O post de Armin Ronacher em seu blog,
«_The Updated Guide to Unicode on Python_ (O Guia Atualizado do Unicode no Python)»
[.small]#&#91;fpy.li/4-37&#93;#
é bastante profundo e destaca algumas armadilhas do Unicode no Python
(Armin não é um grande fã de Python 3).

O capítulo 2 (_Strings and Text_, Strings e Texto) do
«_Python Cookbook, 3rd ed._» [.small]#&#91;fpy.li/pycook3&#93;# (O'Reilly),
de David Beazley e Brian K. Jones, tem várias receitas tratando de normalização de Unicode,
sanitização de texto, e execução de operações orientadas para texto em sequências de bytes.
O capítulo 5 trata de arquivos e E/S, e inclui a
_Recipe 5.17. Writing Bytes to a Text File_ 
(Receita 5.17. Escrevendo Bytes em um Arquivo de Texto_),
mostrando que qualquer arquivo de texto há sempre uma sequência de bytes
que pode ser acessada diretamente quando necessário.
Mais tarde no mesmo livro, o módulo `struct` é usado na
_Recipe 6.11. Reading and Writing Binary Arrays of Structures_
(Receita 6.11. Lendo e Escrevendo Arrays de Registros Binários).

O blog "Python Notes" de Nick Coghlan tem dois posts muito relevantes para esse capítulo:
«_Python 3 and ASCII Compatible Binary Protocols_
(Python 3 e Protocolos Binários Compatíveis com ASCII)» [.small]#&#91;fpy.li/4-38&#93;#
e «_Processing Text Files in Python 3_ (Processando Arquivos de Texto em Python 3)»
[.small]#&#91;fpy.li/4-39&#93;#. Recomendo.

Uma lista de codificações suportadas pelo Python fica em
«_Standard Encodings_ (Codificações Padrão)» [.small]#&#91;fpy.li/3m&#93;#,
na documentação do módulo `codecs`.
Se precisar obter aquela lista de dentro de um programa,
pode ver como isso é feito no script
«`/Tools/unicode/listcodecs.py`» [.small]#&#91;fpy.li/4-41&#93;#,
no código-fonte do CPython.

Os livros 
«_Unicode Explained_ (Unicode Explicado)» [.small]#&#91;fpy.li/4-42&#93;#,
de Jukka K. Korpela (O'Reilly) e
«_Unicode Demystified_ (Unicode Desmistificado)» [.small]#&#91;fpy.li/4-43&#93;#,
de Richard Gillam (Addison-Wesley) não são específicos sobre Python,
nas foram muito úteis para meu estudo dos conceitos do Unicode.
«_Programming with Unicode_ (Programando com Unicode)» [.small]#&#91;fpy.li/4-44&#93;#, de Victor Stinner,
é um livro gratuito, publicado pelo próprio autor, tratando de Unicode em geral,
bem como de ferramentas e APIs no contexto dos principais sistemas operacionais
e algumas linguagens de programação, incluindo Python.

As páginas do W3C
«_Case Folding: An Introduction_ (Case Folding: Uma Introdução)» [.small]#&#91;fpy.li/4-45&#93;# e 
«_Character Model for the World Wide Web: String Matching_
(O Modelo de Caracteres para a World Wide Web: Casamento de Strings)» [.small]#&#91;fpy.li/4-15&#93;# 
tratam de conceitos de normalização,
a primeira uma suave introdução e a segunda uma nota de um grupo de trabalho escrita
no seco jargão dos padrões—o mesmo estilo literário do
«_Unicode Standard Annex 15—Unicode Normalization Forms_ 
(Anexo 15 do Padrão Unicode—Formas de Normalização do Unicode)» [.small]#&#91;fpy.li/4-47&#93;#.
A seção
«_Frequently Asked Questions, Normalization_ (Perguntas Frequentes, Normalização)»
[.small]#&#91;fpy.li/4-48&#93;# 
do 
«_Unicode.org_» [.small]#&#91;fpy.li/4-49&#93;#
é mais fácil de ler, bem como o «_NFC FAQ_» [.small]#&#91;fpy.li/4-50&#93;# de
Mark Davis—autor de vários algoritmos do Unicode e presidente do Unicode Consortium quando essa seção foi escrita.

Em 2016, o((("emojis", "in the Museum of Modern Art"))) Museu de Arte Moderna (MoMA) de New York incluiu ao seu acervo permanente
«_The original emoji_» [.small]#&#91;fpy.li/4-51&#93;# ,
painéis iluminados mostrando os 176 emojis desenhados por Shigetaka Kurita em 1999 para a NTT DOCOMO—uma provedora de telefonia celular japonesa.
Indo mais longe no passado, a «_Emojipedia_» [.small]#&#91;fpy.li/4-52&#93;#  publicou o artigo
«_Correcting the Record on the First Emoji Set_
(Corrigindo o Registro [Histórico] sobre o Primeiro Conjunto de Emojis)» [.small]#&#91;fpy.li/4-53&#93;# ,
atribuindo à SoftBank do Japão o mais antigo conjunto conhecido de emojis,
distribuídos com celulares a partir de 1997.
O conjunto da SoftBank é a fonte de 90 emojis que hoje estão no Unicode,
incluindo o cocô sorridente (`PILE OF POO`, `U+1F4A9`).
O «_emojitracker.com_» [.small]#&#91;fpy.li/4-54&#93;#, de Matthew Rothenberg,
é um painel online mostrando a contagem do uso de emojis no Twitter, atualizado em tempo real.
Quando escrevi isto, o rosto com lágrimas de felicidade (`FACE WITH TEARS OF JOY`, `U+1F602`)
era o emoji mais popular no Twitter, com mais de 3.313.667.315 de ocorrências registradas.

<<<
.Ponto de vista
****

[role="soapbox-title"]
*Nomes não-ASCII no código-fonte: você deveria usá-los?*

Python 3((("Soapbox sidebars", "non-ASCII names in source code")))((("Unicode text versus bytes", "Soapbox discussion")))
permite identificadores não-ASCII no código-fonte:

[source, python]
----
>>> ação = 'PBR'  # ação = stock
>>> ε = 10**-6    # ε = epsilon
----

Algumas pessoas não gostam dessa ideia.
O argumento mais comum é que se limitar aos caracteres ASCII torna a leitura e a edição do código mais fácil para todo mundo.
Esse argumento erra o alvo: você quer que seu código-fonte seja legível e editável pela audiência pretendida,
e isso pode não ser "todo mundo".
Se o código pertence a uma corporação multinacional, ou se é um código aberto e você deseja contribuidores de todo o mundo,
os identificadores devem ser em inglês, e então tudo o que você precisa é do ASCII.

Mas se você é uma professora no Brasil, seus alunos vão achar mais fácil ler código com
variáveis e nomes de função em português, e escritos corretamente.
E eles não terão nenhuma dificuldade para digitar as cedilhas e as vogais acentuadas em seus teclados localizados.

Agora que Python pode interpretar nomes em Unicode, e que o UTF-8 é a codificação padrão para código-fonte,
não vejo motivo para codificar identificadores em português sem acentos, como fazíamos no Python 2,
por necessidade—a menos que seu código tenha que rodar também no Python 2.
Se os nomes estão em português, excluir os acentos não vai tornar o código mais legível para ninguém.

Esse é meu ponto de vista como um brasileiro falante de português,
mas acredito que se aplica além de fronteiras e a outras culturas:
escolha a linguagem humana que torna o código mais legível para sua equipe,
e então use todos os caracteres necessários para a ortografia correta.
****

<<<

****
[role="soapbox-title"]
*O que é "texto puro"?*

Para((("Soapbox sidebars", "plain text"))) qualquer um que lide diariamente com texto em línguas diferentes do inglês
"texto puro" (_plain text_) não significa "ASCII". O «_Unicode Glossary_» [.small]#&#91;fpy.li/4-55&#93;#
define((("plain text"))) _plain text_ dessa forma (nossa tradução):

[quote]
____
Texto codificado por computador que consiste apenas em uma sequência de pontos de código de um dado padrão,
sem qualquer outra informação estrutural ou de formatação.
____

Essa definição começa muito bem, mas não concordo com a parte após a vírgula.
HTML é um ótimo exemplo de um formato de texto puro que inclui informação estrutural e de formatação.
Mas ele ainda é texto puro, porque cada byte em um arquivo desse tipo está lá para representar um caractere de texto,
em geral, usando UTF-8.
Não há bytes com significado não-textual,
como você encontra em documentos _.png_ ou _.xls_,
onde a maioria dos bytes representa valores binários compactos,
como valores RGB ou números de ponto flutuante.
No texto puro, números são representados como sequências de caracteres de dígitos.

Estou escrevendo este livro em um formato de texto puro chamado, ironicamente, «_AsciiDoc_» [.small]#&#91;fpy.li/4-56&#93;#,
que é parte do conjunto de ferramentas da plataforma de publicação de livros
«_Atlas_» [.small]#&#91;fpy.li/4-57&#93;#
da O'Reilly.
Os arquivos fonte de AsciiDoc são texto puro, mas aceitam UTF-8, e não só ASCII.
Do contrário, escrever esse capítulo teria sido ainda mais doloroso.
Apesar do nome equivocado, o formato AsciiDoc é muito bom.footnote:[NT: Esta segunda edição em português foi produzida sem as ferramentas da O'Reilly.
Usamos o «_AsciiDoctor_» [.small]#&#91;fpy.li/4p&#93;#, escrito em Ruby, porque o AsciiDoc original em Python está abandonado.
Em 2022 a O'Reilly usava um fork proprietário do `asciidoc.py`.]

O mundo do Unicode está em constante expansão e, nas fronteiras, as ferramentas de apoio nem sempre existem.
Nem todos os caracteres que eu queria exibir estavam disponíveis nas fontes usadas para renderizar o livro.
Por isso tive que usar capturas de tela do terminal em alguns exemplos desse capítulo.
Por outro lado, os terminais do Ubuntu e do macOS exibem a maioria do texto Unicode
muito bem—incluindo os caracteres kanji da palavra "mojibake": 文字化け.
****

<<<

****
[role="soapbox-title"]
*Como os pontos de código numa str são representados na memória?*

A((("Soapbox sidebars", "code points")))((("code points"))) documentação oficial de Python
evita falar sobre como os pontos de código de uma `str` são armazenados na memória.
Realmente, é um detalhe de implementação.
Em teoria, não importa: qualquer que seja a representação interna,
toda `str` precisa ser codificada para `bytes` na saída.

Na memória, Python 3 armazena cada `str` como uma sequência de pontos de código,
usando um número fixo de bytes por ponto de código,
para permitir o acesso direto eficiente a qualquer caractere ou fatia.

Desde o Python 3.3, ao criar um novo objeto `str`
o interpretador verifica os caracteres no objeto,
e escolhe o layout de memória mais econômico que seja adequado para aquela `str` em particular:
se existirem apenas caracteres na faixa `latin1`, aquela `str` vai usar apenas um byte por ponto de código.
Caso contrário, podem ser usados dois ou quatro bytes por ponto de código, dependendo da `str`.
Isso é uma simplificação; para saber todos os detalhes, dê uma olhada na
«_PEP 393–Flexible String Representation_ (Representação Flexível de Strings)» [.small]#&#91;fpy.li/pep393&#93;#.

A representação flexível de strings é similar à forma como o tipo `int` funciona no Python 3:
se um inteiro cabe em uma palavra da máquina, ele será armazenado em uma palavra da máquina.
Caso contrário, o interpretador muda para uma representação de tamanho variável,
como aquela do tipo `long` de Python 2.
É bom ver as boas ideias se espalhando.

Entretanto, sempre podemos contar com pythonista Armin Ronacher para criticar o Python 3.
Ele me explicou porque, na prática, essa não é uma ideia tão boa assim:
basta um ratinho (`RAT`, `U+1F400`) para inflar um texto,
que de outra forma seria inteiramente ASCII, e transformá-lo em um array gigante na memória,
usando quatro bytes por caractere,
quando um byte seria o suficiente para todos os caracteres exceto o `RAT`.
Além disso, por causa de todas as formas como os caracteres Unicode se combinam,
a facilitade de buscar um caractere arbitrário pela posição é
superestimada—e extrair fatias arbitrárias de texto Unicode é no mínimo ingênuo,
e muitas vezes errado, produzindo mojibake.
Com((("emojis", "increasing issues with"))) os emojis se tornando mais populares,
esses problemas só vão piorar.
****

<<<
