Python str vs tipos de unicode

Trabajando con Python 2.7, me pregunto qué ventaja real hay al usar el tipo unicode lugar de str , ya que ambos parecen ser capaces de mantener cadenas de Unicode. ¿Hay alguna razón especial aparte de poder establecer códigos Unicode en cadenas unicode 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 usando 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 cadena unicode parece estar codificada usando latin1 lugar de utf-8 y la cadena cruda está codificada usando utf-8 ? ¡Estoy aún más confundido ahora! : S

unicode está 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 puede codificarse en una encoding específica para representar el texto como bytes sin procesar (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 la implementación, y no debe preocuparse por ella siempre que sea capaz de representar los puntos de código que desea.

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

Puede pensar en unicode como una representación general de algún texto, que se puede codificar de muchas maneras diferentes en una secuencia de datos binarios representados a través de str .

Nota: en Python 3, unicode fue renombrado a str y hay un nuevo tipo de bytes para una secuencia simple de bytes.

Algunas diferencias que se pueden 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 str tiene un control de nivel inferior en los bytes individuales de una representación de encoding específica, mientras que con unicode solo puede controlar a nivel de punto de código. Por ejemplo puedes hacer:

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

Lo que antes era válido UTF-8, ya no está. 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 alterar la representación interna.

Su terminal pasa a estar configurado a UTF-8.

El hecho de que imprimir a obra sea una coincidencia; está escribiendo bytes UTF-8 sin procesar en el terminal. a es un valor de longitud dos , que contiene dos bytes, valores hexadecimales C3 y A1, mientras que ua es 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 usar los valores de 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 de Unicode para 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 bajos que no son ASCII imprimibles se representan usando valores de escape \x.. también. Esta es la razón por la que una cadena Unicode con un punto de código entre 128 y 255 se parece a la encoding Latin 1. Si tiene una cadena Unicode con puntos de código más allá de U + 00FF, se utiliza una secuencia de escape diferente, \u.... su lugar, con un valor hexadecimal de cuatro dígitos.

Parece que aún no entiendes completamente cuál es la diferencia entre Unicode y una encoding. Por favor, lea los siguientes artículos antes de continuar:

  • El Absoluto Mínimo que todos los desarrolladores de software absolutamente, positivamente deben saber sobre Unicode y conjuntos de caracteres (¡sin excusas!) Por Joel Spolsky

  • El CÓMO de Python Unicode

  • Unicode pragmático de Ned Batchelder

Unicode y las codificaciones son cosas completamente diferentes, 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 usé 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, puedes encontrar los primeros tres caracteres aquí arriba, 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 referirse inequívocamente a cada personaje. Por ejemplo, si estoy hablando de, en lugar de decir “ya sabes, este emoji de risa 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 tienen el formato U+ , luego el valor numérico hexadecimal se rellena 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 de Unicode van desde U + 0000 hasta U + 10FFFF. Eso es 1,114,112 números. 2048 de estos números se utilizan para los sustitutos , por lo tanto, quedan 1,112,064. Esto significa que Unicode puede asignar una ID única (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 Unicode hace es asignar una identificación numérica, llamada punto de código, a cada carácter para una referencia fácil y sin ambigüedades.

Codificaciones

Mapear personajes 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)

Puedes ver todas las asignaciones en esta tabla .

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

Asigna 191 caracteres (los 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)

Puedes ver todas las asignaciones en esta tabla .

UTF-8

Asigna 1,112,064 caracteres (todos los puntos de código Unicode existentes) a patrones de bits 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 los caracteres en cadenas de bits se describe muy bien aquí .

Unicode y codificaciones

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

Por ejemplo, si soy Latin-1 y quiero explicar mi encoding de a, no necesito decir:

“Codifico eso con un aigu (o como llames a esa barra ascendente) como 11100001”

Pero solo puedo decir:

“Yo codifico U + 00E1 como 11100001”

Y si soy UTF-8 , puedo decir:

“Yo, a su vez, codifico U + 00E1 como 11000011 10100001”

Y está 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 encoding, si lo interpretas 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 arreglado 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 dice que hay que interpretar una cadena de bits como 11100001 como un número binario. Míralo como la secuencia de bits que utiliza Latin-1 para codificar el carácter á .

Vuelve a tu pregunta

La encoding utilizada por su intérprete de Python es UTF-8 .

Esto es lo que está pasando en tus 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 se formatea como el número hexadecimal 0xC3 0xA1 y se muestra 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 Python usa internamente para representar el punto de código U + 00E1 en la memoria, y no nos importa):

 >>> ua = u'á' 

Cuando miras el valor de ua , Python te 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 resulta en el patrón de bits 11000011 10100001. De nuevo, 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 resulta en 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 código U + 00E1:

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

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

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