Python str vs tipos Unicode

101

Trabalhando com Python 2.7, estou me perguntando qual é a vantagem real em usar o tipo em unicodevez de str, já que ambos parecem ser capazes de conter strings Unicode. Existe algum motivo especial além de ser capaz de definir códigos Unicode em unicodestrings usando o caractere de escape \?:

Executando um módulo com:

# -*- coding: utf-8 -*-

a = 'á'
ua = u'á'
print a, ua

Resultados em: á, á

EDITAR:

Mais testes usando o shell Python:

>>> a = 'á'
>>> a
'\xc3\xa1'
>>> ua = u'á'
>>> ua
u'\xe1'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> ua
u'\xe1'

Portanto, a unicodestring parece ser codificada usando em latin1vez de utf-8e a string bruta é codificada usando utf-8? Estou ainda mais confuso agora! : S

Caumons
fonte
Não há codificação para unicode, é apenas uma abstração de caracteres Unicode; unicodepode ser convertido para strcom alguma codificação (por exemplo utf-8).
Bin

Respostas:

178

unicodedestina-se a lidar com texto . Texto é uma sequência de pontos de código que pode ser maior do que um único byte . O texto pode ser codificado em uma codificação específica para representar o texto como bytes brutos (por exemplo utf-8, latin-1...).

Observe que unicode não está codificado ! A representação interna usada pelo python é um detalhe de implementação e você não deve se preocupar com isso, desde que seja capaz de representar os pontos de código que você deseja.

Pelo contrário, strem Python 2 é uma sequência simples de bytes . Não representa texto!

Você pode pensar nisso unicodecomo uma representação geral de algum texto, que pode ser codificado de muitas maneiras diferentes em uma sequência de dados binários representados por str.

Nota: No Python 3, unicodefoi renomeado para stre há um novo bytestipo para uma sequência simples de bytes.

Algumas diferenças que você pode ver:

>>> len(u'à')  # a single code point
1
>>> len('à')   # by default utf-8 -> takes two bytes
2
>>> len(u'à'.encode('utf-8'))
2
>>> len(u'à'.encode('latin1'))  # in latin1 it takes one byte
1
>>> print u'à'.encode('utf-8')  # terminal encoding is utf-8
à
>>> print u'à'.encode('latin1') # it cannot understand the latin1 byte

Observe que usando strvocê tem um controle de nível inferior nos bytes únicos de uma representação de codificação específica, enquanto usando unicodevocê só pode controlar no nível de ponto de código. Por exemplo, você pode fazer:

>>> 'àèìòù'
'\xc3\xa0\xc3\xa8\xc3\xac\xc3\xb2\xc3\xb9'
>>> print 'àèìòù'.replace('\xa8', '')
à�ìòù

O que antes era UTF-8 válido, não é mais. Usando uma string Unicode, você não pode operar de forma que a string resultante não seja um texto Unicode válido. Você pode remover um ponto de código, substituir um ponto de código por um ponto de código diferente, etc., mas não pode mexer com a representação interna.

Bakuriu
fonte
4
Muito obrigado pela sua resposta, ajudou muito! A parte mais esclarecedora para mim é: "unicode não está codificado! A representação interna usada por python é um detalhe de implementação, e você não deve se preocupar com isso [...]". Então, ao serializar unicodeobjetos, acho que primeiro temos que explicitamente encode()para o formato de codificação apropriado, já que não sabemos qual está sendo usado internamente para representar o unicodevalor.
Caumons
10
Sim. Quando você deseja salvar algum texto (por exemplo, para um arquivo), você deve representá-lo com bytes, ou seja, deve codificá- lo. Ao recuperar o conteúdo você deve saber a codificação que foi usada, para poder decodificar os bytes em um unicodeobjeto.
Bakuriu
Sinto muito, mas a declaração que unicodenão está codificada está totalmente errada. UTF-16 / UCS-2 e UTF-32 / UCS-4 também são codificações ... e, no futuro, possivelmente mais dessas serão criadas. O ponto é que, só porque você não deve se preocupar com os detalhes de implementação (e, na verdade, você não deve!), Ainda não significa que unicodenão esteja codificado. É, claro. Se isso pode acontecer .decode(), é uma história totalmente diferente.
0xC0000022L
1
@ 0xC0000022L Talvez a frase, tal como está, não seja clara. Deveria dizer: a unicoderepresentação interna do objeto pode ser o que ele quiser, inclusive fora do padrão. Em particular em python3 + unicode se utilizar uma representação interna não padrão que também muda de acordo com os dados contidos. Como tal, não é uma codificação padrão . Unicode como um padrão de texto apenas define pontos de código que são uma representação abstrata de texto, há toneladas de maneiras de codificar unicode na memória, incluindo o utf-X padrão etc. Python usa seu próprio caminho para eficiência.
Bakuriu
1
@ 0xC0000022L Além disso, o fato de UTF-16 ser uma codificação não tem nada a ver com o unicodeobjeto CPython , uma vez que não usa UTF-16, nem UTF-32. Ele usa uma representação ad hoc e, se você quiser codificar os dados em bytes reais, terá que usar encode. Além disso: a linguagem não determina como unicodeé implementado, portanto, diferentes versões ou implementações de python podem (e têm ) diferentes representações internas.
Bakuriu,
38

Unicode e codificações são coisas completamente diferentes e não relacionadas.

Unicode

Atribui um ID numérico a cada caractere:

  • 0x41 → A
  • 0xE1 → á
  • 0x414 → Д

Portanto, o Unicode atribui o número 0x41 a A, 0xE1 a á e 0x414 a Д.

Até a pequena seta → que usei tem seu número Unicode, é 0x2192. E mesmo os emojis têm seus números Unicode, 😂 é 0x1F602.

Você pode pesquisar os números Unicode de todos os caracteres nesta tabela . Em particular, você pode encontrar os primeiros três caracteres acima aqui , a seta aqui e o emoji aqui .

Esses números atribuídos a todos os caracteres por Unicode são chamados de pontos de código .

O objetivo de tudo isso é fornecer um meio de se referir inequivocamente a cada personagem. Por exemplo, se estou falando sobre 😂, em vez de dizer "você sabe, esse emoji rindo com lágrimas" , posso apenas dizer, ponto de código Unicode 0x1F602 . Mais fácil, certo?

Observe que os pontos de código Unicode geralmente são formatados com um inicial e U+, em seguida, o valor numérico hexadecimal preenchido com pelo menos 4 dígitos. Portanto, os exemplos acima seriam U + 0041, U + 00E1, U + 0414, U + 2192, U + 1F602.

Os pontos de código Unicode variam de U + 0000 a U + 10FFFF. Isso é 1.114.112 números. 2.048 desses números são usados ​​para substitutos , portanto, permanecem 1.112.064. Isso significa que o Unicode pode atribuir um ID exclusivo (ponto de código) a 1.112.064 caracteres distintos. Nem todos esses pontos de código foram atribuídos a um caractere ainda, e o Unicode é estendido continuamente (por exemplo, quando novos emojis são introduzidos).

O importante a lembrar é que tudo o que o Unicode faz é atribuir um ID numérico, chamado ponto de código, a cada caractere para uma referência fácil e inequívoca.

Codificações

Mapeie caracteres para padrões de bits.

Esses padrões de bits são usados ​​para representar os caracteres na memória do computador ou no disco.

Existem muitas codificações diferentes que abrangem diferentes subconjuntos de caracteres. No mundo de língua inglesa, as codificações mais comuns são as seguintes:

ASCII

Mapas de 128 caracteres (pontos de código U + 0000 a U + 007F) para padrões de bits de comprimento 7.

Exemplo:

  • a → 1100001 (0x61)

Você pode ver todos os mapeamentos nesta tabela .

ISO 8859-1 (também conhecido como Latin-1)

Mapas de 191 caracteres (pontos de código U + 0020 para U + 007E e U + 00A0 para U + 00FF) para padrões de bits de comprimento 8.

Exemplo:

  • a → 01100001 (0x61)
  • á → 11100001 (0xE1)

Você pode ver todos os mapeamentos nesta tabela .

UTF-8

Mapas 1.112.064 caracteres (todos os pontos de código Unicode existentes) para padrões de bits de comprimento 8, 16, 24 ou 32 bits (ou seja, 1, 2, 3 ou 4 bytes).

Exemplo:

  • a → 01100001 (0x61)
  • á → 11000011 10100001 (0xC3 0xA1)
  • ≠ → 11100010 10001001 10100000 (0xE2 0x89 0xA0)
  • 😂 → 11110000 10011111 10011000 10000010 (0xF0 0x9F 0x98 0x82)

A maneira como o UTF-8 codifica caracteres para sequências de bits é muito bem descrita aqui .

Unicode e codificações

Observando os exemplos acima, fica claro como o Unicode é útil.

Por exemplo, se eu sou Latin-1 e desejo explicar minha codificação de á, não preciso dizer:

"Codifico isso a com um aigu (ou como você chama essa barra ascendente) como 11100001"

Mas posso apenas dizer:

"Codifico U + 00E1 como 11100001"

E se eu for UTF-8 , posso dizer:

"Eu, por sua vez, codifico U + 00E1 como 11000011 10100001"

E é inequivocamente claro para todos a qual personagem queremos dizer.

Agora, para a confusão que muitas vezes surge

É verdade que às vezes o padrão de bits de uma codificação, se você interpretá-lo como um número binário, é o mesmo que o ponto de código Unicode desse caractere.

Por exemplo:

  • ASCII codifica a como 1100001, que você pode interpretar como o número hexadecimal 0x61 , e o ponto de código Unicode de a é U + 0061 .
  • Latin-1 codifica á como 11100001, que você pode interpretar como o número hexadecimal 0xE1 , e o ponto de código Unicode de á é U + 00E1 .

Claro, isso foi arranjado assim de propósito por conveniência. Mas você deve olhar para isso como uma pura coincidência . O padrão de bits usado para representar um caractere na memória não está vinculado de forma alguma ao ponto de código Unicode desse caractere.

Ninguém diz que você deve interpretar uma string de bits como 11100001 como um número binário. Basta olhar para ele como a sequência de bits que Latin-1 usa para codificar o caractere á .

De volta à sua pergunta

A codificação usada pelo seu interpretador Python é UTF-8 .

Aqui está o que está acontecendo em seus exemplos:

Exemplo 1

O seguinte codifica o caractere á em UTF-8. Isso resulta na sequência de bits 11000011 10100001, que é salva na variável a.

>>> a = 'á'

Quando você olha para o valor de a, seu conteúdo 11000011 10100001 é formatado como o número hexadecimal 0xC3 0xA1 e gerado como '\xc3\xa1':

>>> a
'\xc3\xa1'

Exemplo 2

O seguinte salva o ponto de código Unicode de á, que é U + 00E1, na variável ua(não sabemos qual formato de dados o Python usa internamente para representar o ponto de código U + 00E1 na memória e isso não é importante para nós):

>>> ua = u'á'

Quando você olha para o valor de ua, Python informa que ele contém o ponto de código U + 00E1:

>>> ua
u'\xe1'

Exemplo 3

O seguinte codifica o ponto de código Unicode U + 00E1 (representando o caractere á) com UTF-8, o que resulta no padrão de bits 11000011 10100001. Novamente, para a saída, este padrão de bits é representado como o número hexadecimal 0xC3 0xA1:

>>> ua.encode('utf-8')
'\xc3\xa1'

Exemplo 4

O seguinte codifica o ponto de código Unicode U + 00E1 (representando o caractere á) com Latin-1, o que resulta no padrão de bits 11100001. Para a saída, este padrão de bits é representado como o número hexadecimal 0xE1, que por coincidência é o mesmo que o inicial ponto de código U + 00E1:

>>> ua.encode('latin1')
'\xe1'

Não há relação entre o objeto Unicode uae a codificação Latin-1. Que o ponto de código de á seja U + 00E1 e a codificação Latin-1 de á seja 0xE1 (se você interpretar o padrão de bits da codificação como um número binário) é pura coincidência.

Weibeld
fonte
31

Acontece que seu terminal está configurado para UTF-8.

O fato de que a impressão afunciona é uma coincidência; você está gravando bytes UTF-8 brutos no terminal. aé um valor de comprimento dois , contendo dois bytes, valores hexadecimais C3 e A1, enquanto uaé um valor Unicode de comprimento um , contendo um ponto de código U + 00E1.

Essa diferença de comprimento é um dos principais motivos para usar valores Unicode; você não pode medir facilmente o número de caracteres de texto em uma string de bytes; O len()de uma string de byte informa quantos bytes foram usados, não quantos caracteres foram codificados.

Você pode ver a diferença quando codifica o valor Unicode para diferentes codificações de saída:

>>> a = 'á'
>>> ua = u'á'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> a
'\xc3\xa1'

Observe que os primeiros 256 pontos de código do padrão Unicode correspondem ao padrão Latino 1, portanto, o ponto de código U + 00E1 é codificado em Latim 1 como um byte com valor hexadecimal E1.

Além disso, o Python usa códigos de escape em representações de strings Unicode e de bytes semelhantes, e pontos de código baixo que não são ASCII imprimíveis são representados usando \x..valores de escape também. É por isso que uma seqüência de caracteres Unicode com um ponto de código entre 128 e 255 olhares apenas como o 1 de codificação Latina. Se você tiver uma string Unicode com pontos de código além de U + 00FF, uma sequência de escape diferente \u....será usada, com um valor hexadecimal de quatro dígitos.

Parece que você ainda não entendeu completamente qual é a diferença entre Unicode e uma codificação. Leia os seguintes artigos antes de continuar:

Martijn Pieters
fonte
Eu editei minha pergunta com mais testes. Tenho lido unicode e as diferentes codificações por um tempo e acho que entendo a teoria, mas quando realmente
testo o
1
A codificação latin-1 corresponde aos primeiros 256 pontos de código do padrão Unicode. É por isso que U + 00E1 é codificado \xe1em latim 1.
Martijn Pieters
2
Esse é o aspecto mais importante do Unicode. Não é uma codificação . É um texto. Unicode é um padrão que inclui muito, muito mais, como informações sobre o que os pontos de código são números, ou espaços em branco ou outras categorias, devem ser exibidos da esquerda para a direita ou da direita para a esquerda, etc. etc. etc. etc.
Martijn Pieters
1
É como dizer que o Unicode é como uma "Interface" e a Codificação é como uma "Implementação" real.
Caumons
2
@Varun: você deve estar usando uma construção estreita do Python 2, que usa UCS-2 internamente e deturpa qualquer coisa acima de U + FFFF como tendo comprimento dois. Python 3 e uma construção UCS-2 (larga) mostrarão que o comprimento é realmente 1.
Martijn Pieters
2

Quando você define a como unicode, os caracteres a e á são iguais. Caso contrário, á conta como dois caracteres. Experimente len (a) e len (au). Além disso, pode ser necessário ter a codificação ao trabalhar com outros ambientes. Por exemplo, se você usar md5, obterá valores diferentes para a e ua

Ali Rasim Kocal
fonte