Eu executo este snippet duas vezes, no terminal do Ubuntu (codificação definida para utf-8), uma vez com ./test.py
e depois com ./test.py >out.txt
:
uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni
Sem redirecionamento, ele imprime lixo. Com o redirecionamento, recebo um UnicodeDecodeError. Alguém pode explicar por que recebo o erro apenas no segundo caso, ou melhor ainda, dar uma explicação detalhada do que está acontecendo por trás da cortina em ambos os casos?
Respostas:
A chave para esses problemas de codificação é entender que existem, em princípio, dois conceitos distintos de "string" : (1) string de caracteres e (2) string / array de bytes. Esta distinção foi ignorada por muito tempo devido à onipresença histórica de codificações com não mais que 256 caracteres (ASCII, Latin-1, Windows-1252, Mac OS Roman, ...): essas codificações mapeiam um conjunto de caracteres comuns para números entre 0 e 255 (ou seja, bytes); a troca relativamente limitada de arquivos antes do advento da web tornou tolerável essa situação de codificações incompatíveis, já que a maioria dos programas poderia ignorar o fato de que havia várias codificações, desde que produzissem texto que permanecesse no mesmo sistema operacional: tais programas simplesmente tratar o texto como bytes (por meio da codificação usada pelo sistema operacional). A visão moderna correta separa adequadamente esses dois conceitos de string, com base nos dois pontos a seguir:
A maioria dos personagens não está relacionada a computadores : pode-se desenhá-los em um quadro-negro, etc., como por exemplo بايثون, 中 蟒 e 🐍. "Caracteres" para máquinas também incluem "instruções de desenho" como, por exemplo, espaços, retorno de carro, instruções para definir a direção da escrita (para árabe, etc.), acentos, etc. Uma lista muito grande de caracteres está incluída no padrão Unicode ; ele cobre a maioria dos personagens conhecidos.
Por outro lado, os computadores precisam representar caracteres abstratos de alguma forma: para isso, eles usam matrizes de bytes (números entre 0 e 255 incluídos), porque sua memória vem em blocos de bytes. O processo necessário que converte caracteres em bytes é chamado de codificação . Assim, um computador requer uma codificação para representar os caracteres. Qualquer texto presente em seu computador é codificado (até que seja exibido), seja ele enviado para um terminal (que espera caracteres codificados de uma forma específica), ou salvo em um arquivo. Para serem exibidos ou "compreendidos" adequadamente (por exemplo, pelo interpretador Python), os fluxos de bytes são decodificados em caracteres. Algumas codificações(UTF-8, UTF-16, ...) são definidos por Unicode para sua lista de caracteres (Unicode, portanto, define uma lista de caracteres e codificações para esses caracteres - ainda há lugares onde se vê a expressão "codificação Unicode" como um forma de se referir ao ubíquo UTF-8, mas essa é uma terminologia incorreta, pois o Unicode fornece várias codificações).
Em resumo, os computadores precisam representar internamente os caracteres com bytes , e o fazem por meio de duas operações:
Algumas codificações não podem codificar todos os caracteres (por exemplo, ASCII), enquanto (algumas) codificações Unicode permitem que você codifique todos os caracteres Unicode. A codificação também não é necessariamente exclusiva , porque alguns caracteres podem ser representados diretamente ou como uma combinação (por exemplo, de um caractere base e de acentos).
Observe que o conceito de nova linha adiciona uma camada de complicação , uma vez que pode ser representado por caracteres diferentes (de controle) que dependem do sistema operacional (essa é a razão para o modo de leitura de arquivo de nova linha universal do Python ).
Agora, o que chamei de "caractere" acima é o que o Unicode chama de " caractere percebido pelo usuário ". Um único caractere percebido pelo usuário às vezes pode ser representado em Unicode combinando partes de caracteres (caractere base, acentos, ...) encontrados em diferentes índices na lista Unicode, que são chamados de " pontos de código " - esses pontos de código podem ser combinados para formar um "aglomerado de grafemas". Unicode, portanto, leva a um terceiro conceito de string, feito de uma sequência de pontos de código Unicode, que fica entre as strings de byte e de caracteres, e que está mais próximo do último. Vou chamá-los de " strings Unicode " (como em Python 2).
Enquanto o Python pode imprimir strings de caracteres (percebidos pelo usuário), strings não-byte do Python são essencialmente sequências de pontos de código Unicode , não de caracteres percebidos pelo usuário. Os valores de ponto de código são aqueles usados na sintaxe de string do Python
\u
e\U
Unicode. Eles não devem ser confundidos com a codificação de um caractere (e não precisam ter nenhum relacionamento com ele: os pontos de código Unicode podem ser codificados de várias maneiras).Isso tem uma consequência importante: o comprimento de uma string Python (Unicode) é seu número de pontos de código, que nem sempre é o número de caracteres percebidos pelo usuário : assim
s = "\u1100\u1161\u11a8"; print(s, "len", len(s))
(Python 3) dá각 len 3
apesar des
ter um único caractere percebido pelo usuário (coreano) caractere (porque é representado com 3 pontos de código - mesmo que não seja necessário, comoprint("\uac01")
mostra). No entanto, em muitas circunstâncias práticas, o comprimento de uma string é o número de caracteres percebidos pelo usuário, porque muitos caracteres são normalmente armazenados pelo Python como um único ponto de código Unicode.No Python 2 , strings Unicode são chamadas de… "strings Unicode" (
unicode
tipo, forma literalu"…"
), enquanto matrizes de bytes são "strings" (str
tipo, onde a matriz de bytes pode, por exemplo, ser construída com literais de string"…"
). No Python 3 , as strings Unicode são simplesmente chamadas de "strings" (str
tipo, forma literal"…"
), enquanto as matrizes de bytes são "bytes" (bytes
tipo, forma literalb"…"
). Como consequência, algo como"🐍"[0]
dá um resultado diferente em Python 2 ('\xf0'
, um byte) e Python 3 ("🐍"
, o primeiro e único caractere).Com esses poucos pontos-chave, você deve ser capaz de entender a maioria das questões relacionadas à codificação!
Normalmente, quando você imprime
u"…"
em um terminal , você não deve pegar lixo: Python conhece a codificação de seu terminal. Na verdade, você pode verificar qual codificação o terminal espera:Se seus caracteres de entrada puderem ser codificados com a codificação do terminal, o Python fará isso e enviará os bytes correspondentes ao seu terminal sem reclamar. O terminal fará o possível para exibir os caracteres após decodificar os bytes de entrada (na pior das hipóteses, a fonte do terminal não possui alguns dos caracteres e, em vez disso, imprimirá algum tipo de espaço em branco).
Se os caracteres de entrada não puderem ser codificados com a codificação do terminal, isso significa que o terminal não está configurado para exibir esses caracteres. Python irá reclamar (em Python com um uma
UnicodeEncodeError
vez que a string de caracteres não pode ser codificada de uma forma que se adapte ao seu terminal). A única solução possível é usar um terminal que pode exibir os caracteres (seja configurando o terminal para que ele aceite uma codificação que possa representar seus caracteres, ou usando um programa de terminal diferente). Isso é importante quando você distribui programas que podem ser usados em diferentes ambientes: as mensagens impressas devem ser representadas no terminal do usuário. Às vezes, é melhor usar strings que contenham apenas caracteres ASCII.No entanto, quando você redireciona ou canaliza a saída de seu programa, geralmente não é possível saber qual é a codificação de entrada do programa receptor, e o código acima retorna alguma codificação padrão: Nenhum (Python 2.7) ou UTF-8 ( Python 3):
A codificação de stdin, stdout e stderr pode, no entanto, ser definida por
PYTHONIOENCODING
meio da variável de ambiente, se necessário:Se a impressão para um terminal não produzir o que você espera, você pode verificar se a codificação UTF-8 que você colocou manualmente está correta; por exemplo, seu primeiro caractere (
\u001A
) não pode ser impresso, se não estou enganado .Em http://wiki.python.org/moin/PrintFails , você pode encontrar uma solução como a seguinte, para Python 2.x:
Para Python 3, você pode verificar uma das perguntas feitas anteriormente no StackOverflow.
fonte
Python sempre codifica strings Unicode ao gravar em um terminal, arquivo, canal, etc. Ao gravar em um terminal, o Python geralmente pode determinar a codificação do terminal e usá-lo corretamente. Ao gravar em um arquivo ou canal, o padrão do Python é a codificação 'ascii', a menos que seja explicitamente informado o contrário. Pode-se dizer ao Python o que fazer ao canalizar a saída por
PYTHONIOENCODING
meio da variável de ambiente. Um shell pode definir essa variável antes de redirecionar a saída do Python para um arquivo ou canal para que a codificação correta seja conhecida.No seu caso, você imprimiu 4 caracteres incomuns que seu terminal não suportava em sua fonte. Aqui estão alguns exemplos para ajudar a explicar o comportamento, com caracteres que são realmente suportados pelo meu terminal (que usa cp437, não UTF-8).
Exemplo 1
Observe que o
#coding
comentário indica a codificação na qual o arquivo de origem é salvo. Escolhi utf8 para poder suportar caracteres na origem que meu terminal não podia. Codificação redirecionada para stderr para que possa ser vista quando redirecionada para um arquivo.Saída (executado diretamente do terminal)
Python determinou corretamente a codificação do terminal.
Saída (redirecionado para o arquivo)
Python não conseguiu determinar a codificação (Nenhuma), então usou o padrão 'ascii'. ASCII suporta apenas a conversão dos primeiros 128 caracteres de Unicode.
Saída (redirecionado para o arquivo, PYTHONIOENCODING = cp437)
e meu arquivo de saída estava correto:
Exemplo 2
Agora, colocarei um caractere na fonte que não é compatível com meu terminal:
Saída (executado diretamente do terminal)
Meu terminal não entendia o último caractere chinês.
Saída (executar diretamente, PYTHONIOENCODING = 437: substituir)
Manipuladores de erros podem ser especificados com a codificação. Neste caso, caracteres desconhecidos foram substituídos por
?
.ignore
exmlcharrefreplace
algumas outras opções. Ao usar UTF8 (que suporta a codificação de todos os caracteres Unicode), as substituições nunca serão feitas, mas a fonte usada para exibir os caracteres ainda deve suportá-los.fonte
PYTHONIOENCODING
. Fazerprint string.encode("UTF-8")
como sugerido por @Ismail funcionou para mim.chcp
página de código não os ofereça . Para evitarUnicodeEncodeError: 'charmap'
, você pode instalar owin-unicode-console
pacote.PYTHONIOENCODING=utf-8
resolve o problema.Codifique durante a impressão
Isso ocorre porque quando você executa o script manualmente, o python o codifica antes de enviá-lo para o terminal, quando você o canaliza, o python não o codifica sozinho, então você tem que codificar manualmente ao fazer E / S.
fonte
win-unicode-console
(Windows), ou aceite um parâmetro de linha de comando (se for necessário).