Python str vs tipos unicode

101

Trabajando con Python 2.7, me pregunto qué ventaja real hay en usar el tipo en unicodelugar de str, ya que ambos parecen poder contener cadenas Unicode. ¿Hay alguna razón especial además de poder establecer códigos Unicode en unicodecadenas usando el carácter de escape \?:

Ejecutando un módulo con:

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

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

Resultados en: á, á

EDITAR:

Más pruebas con el shell de Python:

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

Entonces, la unicodecadena parece estar codificada usando en latin1lugar de utf-8y la cadena sin procesar está codificada usando utf-8? ¡Estoy aún más confundido ahora! : S

Caumons
fuente
No hay codificación para unicode, es sólo una abstracción de caracteres Unicode; unicodese puede convertir strcon alguna codificación (por ejemplo utf-8).
Bin

Respuestas:

178

unicodeestá destinado a manejar texto . El texto es una secuencia de puntos de código que puede ser más grande que un solo byte . El texto se puede codificar en una codificación específica para representar el texto como bytes sin formato (por ejemplo utf-8, latin-1...).

¡Tenga en cuenta que unicode no está codificado ! La representación interna utilizada por Python es un detalle de implementación, y no debería preocuparse por ella siempre que pueda representar los puntos de código que desea.

Por el contrario, stren Python 2 es una secuencia simple de bytes . ¡No representa texto!

Puede pensar unicodeen una representación general de un texto, que se puede codificar de muchas formas diferentes en una secuencia de datos binarios representados mediante str.

Nota: En Python 3, unicodese cambió el nombre a stry hay un nuevo bytestipo para una secuencia simple de bytes.

Algunas diferencias que puedes 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

Tenga en cuenta que al usar strtiene un control de nivel inferior en los bytes individuales de una representación de codificación específica, mientras que al usar unicodesolo puede controlar en el nivel de punto de código. Por ejemplo, puede hacer:

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

Lo que antes era válido UTF-8, ya no lo es. Al usar una cadena Unicode, no puede operar de tal manera que la cadena resultante no sea un texto Unicode válido. Puede eliminar un punto de código, reemplazar un punto de código con un punto de código diferente, etc. pero no puede meterse con la representación interna.

Bakuriu
fuente
4
Muchas gracias por tu respuesta, ¡ayudó mucho! La parte más aclaratoria para mí es: "¡Unicode no está codificado! La representación interna utilizada por Python es un detalle de implementación, y no debería preocuparte por ello [...]". Entonces, al serializar unicodeobjetos, supongo que primero tenemos que explícitamente encode()en el formato de codificación adecuado, ya que no sabemos cuál se está utilizando internamente para representar el unicodevalor.
Caumons
10
Si. Cuando desee guardar algún texto (por ejemplo, en un archivo) debe representarlo con bytes, es decir, debe codificarlo . Al recuperar el contenido, debe conocer la codificación que se utilizó para poder decodificar los bytes en un unicodeobjeto.
Bakuriu
Lo siento, pero la declaración que unicodeno está codificada es completamente incorrecta. UTF-16 / UCS-2 y UTF-32 / UCS-4 también son codificaciones ... y en el futuro posiblemente se creen más de estas. El punto es que el hecho de que no debería preocuparse por los detalles de la implementación (¡y, de hecho, no debería!), Todavía no significa que unicodeno esté codificado. Por supuesto que lo es. Si puede ser .decode()o no es una historia completamente diferente.
0xC0000022L
1
@ 0xC0000022L Quizás la oración tal como está no está clara. Debería decir: la unicoderepresentación interna del objeto puede ser lo que quiera, incluida una no estándar. En particular, en python3 + unicode qué utilizar una representación interna no estándar que también cambia en función de los datos contenidos. Como tal, no es una codificación estándar . Unicode como estándar de texto solo define puntos de código que son una representación abstracta de texto, hay muchas formas de codificar unicode en la memoria, incluido el utf-X estándar, etc. Python usa su propia forma de eficiencia.
Bakuriu
1
@ 0xC0000022L Además, el hecho de que UTF-16 sea una codificación no tiene nada que ver con el unicodeobjeto de CPython , ya que no usa UTF-16, ni UTF-32. Utiliza una representación ad hoc, y si desea codificar los datos en bytes reales, debe usarlos encode. Además: el lenguaje no exige cómo unicodese implementa, por lo que diferentes versiones o implementaciones de Python pueden (y tienen ) una representación interna diferente.
Bakuriu
38

Unicode y las codificaciones son cosas completamente diferentes y no relacionadas.

Unicode

Asigna una identificación numérica a cada carácter:

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

Entonces, Unicode asigna el número 0x41 a A, 0xE1 a á y 0x414 a Д.

Incluso la pequeña flecha → que utilicé tiene su número Unicode, es 0x2192. E incluso los emojis tienen sus números Unicode, 😂 es 0x1F602.

Puede buscar los números Unicode de todos los caracteres en esta tabla . En particular, puede encontrar los primeros tres caracteres arriba aquí , la flecha aquí y el emoji aquí .

Estos números asignados a todos los caracteres por Unicode se denominan puntos de código .

El propósito de todo esto es proporcionar un medio para hacer referencia inequívoca a cada carácter. Por ejemplo, si estoy hablando de 😂, en lugar de decir "ya sabes, este emoji riendo con lágrimas" , solo puedo decir, punto de código Unicode 0x1F602 . Más fácil, ¿verdad?

Tenga en cuenta que los puntos de código Unicode generalmente se formatean con un principio U+, luego el valor numérico hexadecimal se completa con al menos 4 dígitos. Entonces, los ejemplos anteriores serían U + 0041, U + 00E1, U + 0414, U + 2192, U + 1F602.

Los puntos de código Unicode van desde U + 0000 a U + 10FFFF. Eso es 1,114,112 números. 2048 de estos números se utilizan para sustitutos , por lo que quedan 1,112,064. Esto significa que Unicode puede asignar un ID único (punto de código) a 1,112,064 caracteres distintos. Aún no todos estos puntos de código están asignados a un carácter y Unicode se extiende continuamente (por ejemplo, cuando se introducen nuevos emojis).

Lo importante a recordar es que todo lo que hace Unicode es asignar un ID numérico, llamado punto de código, a cada carácter para una referencia fácil y sin ambigüedades.

Codificaciones

Asignar caracteres a patrones de bits.

Estos patrones de bits se utilizan para representar los caracteres en la memoria de la computadora o en el disco.

Hay muchas codificaciones diferentes que cubren diferentes subconjuntos de caracteres. En el mundo de habla inglesa, las codificaciones más comunes son las siguientes:

ASCII

Asigna 128 caracteres (puntos de código U + 0000 a U + 007F) a patrones de bits de longitud 7.

Ejemplo:

  • a → 1100001 (0x61)

Puede ver todas las asignaciones en esta tabla .

ISO 8859-1 (también conocido como Latin-1)

Asigna 191 caracteres (puntos de código U + 0020 a U + 007E y U + 00A0 a U + 00FF) a patrones de bits de longitud 8.

Ejemplo:

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

Puede ver todas las asignaciones en esta tabla .

UTF-8

Mapas 1,112,064 caracteres (todos los puntos de código Unicode existentes) a patrones de bits de cualquiera de longitud 8, 16, 24, o 32 bits (es decir, 1, 2, 3, o 4 bytes).

Ejemplo:

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

La forma en que UTF-8 codifica caracteres en cadenas de bits se describe muy bien aquí .

Unicode y codificaciones

Al observar los ejemplos anteriores, queda claro cómo es útil Unicode.

Por ejemplo, si soy Latin-1 y quiero explicar mi codificación de á, no necesito decir:

"Codifico que a con un aigu (o como se llame a esa barra ascendente) como 11100001"

Pero solo puedo decir:

"Codifico U + 00E1 como 11100001"

Y si soy UTF-8 , puedo decir:

"Yo, a mi vez, codifico U + 00E1 como 11000011 10100001"

Y es inequívocamente claro para todos a qué personaje nos referimos.

Ahora a la confusión que a menudo surge

Es cierto que a veces el patrón de bits de una codificación, si lo interpreta como un número binario, es el mismo que el punto de código Unicode de este carácter.

Por ejemplo:

  • ASCII codifica a como 1100001, que puede interpretar como el número hexadecimal 0x61 , y el punto de código Unicode de a es U + 0061 .
  • Latin-1 codifica á como 11100001, que puede interpretar como el número hexadecimal 0xE1 , y el punto de código Unicode de á es U + 00E1 .

Por supuesto, esto se ha organizado así a propósito para mayor comodidad. Pero deberías verlo como una pura coincidencia . El patrón de bits utilizado para representar un carácter en la memoria no está vinculado de ninguna manera al punto de código Unicode de este carácter.

Nadie incluso dice que tienes que interpretar una cadena de bits como 11100001 como un número binario. Míralo como la secuencia de bits que Latin-1 usa para codificar el carácter á .

De vuelta a tu pregunta

La codificación que usa su intérprete de Python es UTF-8 .

Esto es lo que sucede en sus ejemplos:

Ejemplo 1

Lo siguiente codifica el carácter á en UTF-8. Esto da como resultado la cadena de bits 11000011 10100001, que se guarda en la variable a.

>>> a = 'á'

Cuando observa el valor de a, su contenido 11000011 10100001 tiene el formato del número hexadecimal 0xC3 0xA1 y se genera como '\xc3\xa1':

>>> a
'\xc3\xa1'

Ejemplo 2

Lo siguiente guarda el punto de código Unicode de á, que es U + 00E1, en la variable ua(no sabemos qué formato de datos usa Python internamente para representar el punto de código U + 00E1 en la memoria, y no es importante para nosotros):

>>> ua = u'á'

Cuando observa el valor de ua, Python le dice que contiene el punto de código U + 00E1:

>>> ua
u'\xe1'

Ejemplo 3

Lo siguiente codifica el punto de código Unicode U + 00E1 (que representa el carácter á) con UTF-8, lo que da como resultado el patrón de bits 11000011 10100001. Nuevamente, para la salida, este patrón de bits se representa como el número hexadecimal 0xC3 0xA1:

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

Ejemplo 4

Lo siguiente codifica el punto de código Unicode U + 00E1 (que representa el carácter á) con Latin-1, lo que da como resultado el patrón de bits 11100001. Para la salida, este patrón de bits se representa como el número hexadecimal 0xE1, que por coincidencia es el mismo que el inicial punto de código U + 00E1:

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

No hay relación entre el objeto Unicode uay la codificación Latin-1. Que el punto de código de á sea U + 00E1 y la codificación Latin-1 de á sea 0xE1 (si interpreta el patrón de bits de la codificación como un número binario) es una pura coincidencia.

weibeld
fuente
31

Su terminal está configurado para UTF-8.

El hecho de que la imprenta afuncione es una coincidencia; está escribiendo bytes UTF-8 sin procesar en el terminal. aes un valor de longitud dos , que contiene dos bytes, valores hexadecimales C3 y A1, mientras que uaes un valor unicode de longitud uno , que contiene un punto de código U + 00E1.

Esta diferencia de longitud es una de las principales razones para utilizar valores Unicode; no puede medir fácilmente el número de caracteres de texto en una cadena de bytes; el len()de una cadena de bytes le dice cuántos bytes se usaron, no cuántos caracteres se codificaron.

Puede ver la diferencia cuando codifica el valor Unicode en diferentes codificaciones de salida:

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

Tenga en cuenta que los primeros 256 puntos de código del estándar Unicode coinciden con el estándar Latin 1, por lo que el punto de código U + 00E1 se codifica en Latin 1 como un byte con valor hexadecimal E1.

Además, Python usa códigos de escape en representaciones de cadenas de bytes y unicode por igual, y los puntos de código bajo que no son ASCII imprimibles también se representan usando \x..valores de escape. Es por eso que una cadena Unicode con un punto de código entre 128 y 255 se parece a la codificación Latin 1. Si tiene una cadena Unicode con puntos de código más allá de U + 00FF, \u....se usa una secuencia de escape diferente , con un valor hexadecimal de cuatro dígitos.

Parece que aún no comprende completamente cuál es la diferencia entre Unicode y una codificación. Lea los siguientes artículos antes de continuar:

Martijn Pieters
fuente
He editado mi pregunta con más pruebas. He estado leyendo para Unicode y las diferentes codificaciones durante un tiempo y creo que entiendo la teoría, pero cuando realmente pruebo el código Python no entiendo lo que está sucediendo
Caumons
1
La codificación latin-1 coincide con los primeros 256 puntos de código del estándar Unicode. Es por eso que U + 00E1 codifica \xe1en latín 1.
Martijn Pieters
2
Ese es el aspecto más importante de Unicode. No es una codificación . Es texto. Unicode es un estándar que incluye mucho, mucho más, como información sobre qué puntos de código son números, espacios en blanco u otras categorías, deben mostrarse de izquierda a derecha o de derecha a izquierda, etc., etc., etc.
Martijn Pieters
1
Es como decir que Unicode es como una "interfaz" y la codificación es como una "implementación" real.
Caumons
2
@Varun: debes usar una compilación estrecha de Python 2, que usa UCS-2 internamente y tergiversa cualquier cosa sobre U + FFFF como si tuviera una longitud dos. Python 3 y una compilación UCS-2 (ancha) le mostrarán que la longitud es realmente 1.
Martijn Pieters
2

Cuando define a como Unicode, los caracteres ay á son iguales. De lo contrario, á cuenta como dos caracteres. Pruebe len (a) y len (au). Además de eso, es posible que deba tener la codificación cuando trabaje con otros entornos. Por ejemplo, si usa md5, obtiene diferentes valores para ay ua

Ali Rasim Kocal
fuente