¿Por qué json.dumps escapa de caracteres no-ascii con “\ uxxxx”?

En Python 2, la función json.dumps() asegurará que todos los caracteres que no son \uxxxx se escapan como \uxxxx .

Python 2 Json

Pero, ¿no es esto bastante confuso porque \uxxxx es un carácter Unicode y debe usarse dentro de una cadena Unicode?

La salida de json.dumps() es una str , que es una cadena de bytes en Python 2. ¿Y por lo tanto no debería escapar de los caracteres como \xhh ?

 >>> unicode_string = u"\u00f8" >>> print unicode_string ø >>> print json.dumps(unicode_string) "\u00f8" >>> unicode_string.encode("utf8") '\xc3\xb8' 

Ese es exactamente el punto. Obtiene una cadena de bytes, no una cadena Unicode. Por lo tanto, los personajes de Unicode deben ser escapados para sobrevivir. JSON permite el escape y, por lo tanto, presenta una forma segura de representar los caracteres Unicode.

¿Por qué json.dumps escapa de caracteres no-ascii con “\ uxxxx”?

Python 2 puede mezclar secuencias de caracteres solo ASCII y cadenas Unicode juntas.

Podría ser una optimización prematura. Las cadenas Unicode pueden requerir de 2 a 4 veces más memoria que las secuencias de caracteres correspondientes si contienen caracteres principalmente en el rango ASCII en Python 2.

Además, incluso hoy en día, print(unicode_string) puede fallar fácilmente si contiene caracteres no-ascii mientras se imprime en la consola de Windows a menos que se instale algo como el paquete de Python win-unicode-console . Puede fallar incluso en Unix si se usa la configuración regional C / POSIX (predeterminada para los servicios init.d , ssh , cron en muchos casos) (eso implica encoding de caracteres C.UTF-8 . Existe C.UTF-8 pero no siempre está disponible y usted Hay que configurarlo explícitamente). Podría explicar por qué es posible que desee ensure_ascii=True en algunos casos.

El formato JSON se define para texto Unicode y, por lo tanto, estrictamente hablando, json.dumps() siempre debe devolver una cadena Unicode, pero puede devolver un bytestring si todos los caracteres están en el rango ASCII ( xml.etree.ElementTree tiene una “optimización” similar). Es confuso que Python 2 permita tratar un bytest solo de ascii como una cadena Unicode en algunos casos (se permiten conversiones implícitas). Python 3 es más estricto (las conversiones implícitas están prohibidas).

Se pueden usar bytestrings solo ASCII en lugar de cadenas Unicode (con posibles caracteres no ASCII) para ahorrar memoria y / o mejorar la interoperabilidad en Python 2.

Para deshabilitar ese comportamiento, use json.dumps(obj, ensure_ascii=False) .


Es importante evitar confundir una cadena Unicode con su representación en el código fuente de Python como una cadena literal de Python o su representación en un archivo como texto JSON.

El formato JSON permite escapar de cualquier carácter, no solo de caracteres Unicode fuera del rango ASCII:

 >>> import json >>> json.loads(r'"\u0061"') u'a' >>> json.loads('"a"') u'a' 

No lo confunda con escapes en los literales de cadena de Python utilizados en el código fuente de Python. u"\u00f8" es un solo carácter Unicode pero "\u00f8" en la salida tiene ocho caracteres (en el código fuente de Python, puede corregirlo como r'"\u00f8"' == '"\\u00f8"' == u'"\\u00f8"' (la barra diagonal inversa es especial tanto en los literales de Python como en el texto de json; puede ocurrir un doble escape ). Además, no hay escapes de \x en JSON:

 >>> json.loads(r'"\x61"') # invalid JSON Traceback (most recent call last): ... ValueError: Invalid \escape: line 1 column 2 (char 1) >>> r'"\x61"' # valid Python literal (6 characters) '"\\x61"' >>> '"\x61"' # valid Python literal with escape sequence (3 characters) '"a"' 

La salida de json.dumps () es una cadena, que es una cadena de bytes en Python 2. ¿Y por lo tanto no debería escapar de los caracteres como \ xhh?

json.dumps(obj, ensure_ascii=True) produce solo caracteres ASCII imprimibles y, por lo tanto, print repr(json.dumps(u"\xf8")) no contendrá \xhh escapes que se usan para representar ( repr() ) no caracteres imprimibles (bytes).

\u escapes pueden ser necesarios incluso para entrada solo ascii:

 #!/usr/bin/env python2 import json print json.dumps(map(unichr, range(128))) 

Salida

 ["\u0000", "\u0001", "\u0002", "\u0003", "\u0004", "\u0005", "\u0006", "\u0007", "\b", "\t", "\n", "\u000b", "\f", "\r", "\u000e", "\u000f", "\u0010", "\u0011", "\u0012", "\u0013", "\u0014", "\u0015", "\u0016", "\u0017", "\u0018", "\u0019", "\u001a", "\u001b", "\u001c", "\u001d", "\u001e", "\u001f", " ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", "\u007f"] 

Pero, ¿no es esto bastante confuso porque \ uxxxx es un carácter Unicode y debe usarse dentro de una cadena Unicode?

\uxxxx son 6 caracteres que pueden interpretarse como un solo carácter en algunos contextos, por ejemplo, en el código fuente de Python u"\uxxxx" es un literal de Python que crea una cadena Unicode en la memoria con un solo carácter Unicode. Pero si ves \uxxxx en un texto json; son seis caracteres que pueden representar un solo carácter Unicode si lo carga ( json.loads() ).

En este punto, debe comprender por qué len(json.loads('"\\\\"')) == 1 .

\u en "\u00f8" no es en realidad una secuencia de escape como \x . El \u es un literal r'\u' . Pero tales cadenas de bytes se pueden convertir fácilmente a Unicode.

Manifestación:

 s = "\u00f8" u = s.decode('unicode-escape') print repr(s), len(s), repr(u), len(u) s = "\u2122" u = s.decode('unicode-escape') print repr(s), len(s), repr(u), len(u) 

salida

 '\\u00f8' 6 u'\xf8' 1 '\\u2122' 6 u'\u2122' 1 

Como JFSebastian menciona en los comentarios, dentro de una cadena de Unicode \u00f8 hay un verdadero código de escape, es decir, en una cadena de Python 3 o en una cadena de Python 2 u"\u00f8" . ¡También tenga cuidado de sus otros comentarios!