Почему json.dumps сбрасывает символы не-ascii с помощью "\ uxxxx"

В Python 2 функция json.dumps() гарантирует, что все символы не-ascii будут экранированы как \uxxxx.

Python 2 Json

Но разве это не сбивает с толку, потому что \uxxxx является символом юникода и должен использоваться внутри строки юникода.

Вывод json.dumps() - это str, который является байтовой строкой в ​​Python 2. И, следовательно, он не должен вызывать символы как \xhh?

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

Ответ 1

Это точно точка. Вы возвращаете байтовую строку, а не строку Unicode. Таким образом, символы Юникода должны быть экранированы, чтобы выжить. Выделение разрешено JSON и, таким образом, представляет безопасный способ представления символов Unicode.

Ответ 2

Почему json.dumps запускает символы не-ascii с помощью "\ uxxxx"

Python 2 может смешивать ascii-only bytestrings и строки Unicode вместе.

Это может быть преждевременная оптимизация. Строки Unicode могут требовать в 2-4 раза больше памяти, чем соответствующие байты, если они содержат символы в основном в диапазоне ASCII в Python 2.

Кроме того, даже сегодня print(unicode_string) может легко выйти из строя, если он содержит символы не-ascii при печати в консоли Windows, если не установлено что-то вроде win-unicode-console Python package. Он может потерпеть неудачу даже в Unix, если используется язык C/POSIX (по умолчанию для служб init.d, ssh, cron) (что подразумевает кодировку символов ascii. Существует C.UTF-8, но она не всегда доступна и вы должны настроить его явно). Это может объяснить, почему вам может понадобиться ensure_ascii=True в некоторых случаях.

Формат JSON определен для текста в Юникоде, и, строго говоря, json.dumps() всегда должен возвращать строку Юникода, но может возвращать байтовую строку, если все символы находятся в диапазоне ASCII (xml.etree.ElementTree имеет аналогичную "оптимизацию" ). Сложно предположить, что Python 2 позволяет обрабатывать ascii-only bytestring как строку Unicode в некоторых случаях (допускаются неявные преобразования). Python 3 более строгий (неявные преобразования запрещены).

Вместо строк Unicode (с возможными символами, отличными от ASCII), вместо строк Unicode могут использоваться только ASCII-байты, чтобы сохранить память и/или улучшить взаимодействие в Python 2.

Чтобы отключить это поведение, используйте json.dumps(obj, ensure_ascii=False).


Важно не путать строку Unicode с ее представлением в исходном коде Python как строковый литерал Python или его представление в файле как текст JSON.

Формат JSON позволяет избежать любого символа, а не только символов Unicode вне диапазона ASCII:

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

Не путайте его с экранами в строковых литералах Python, используемых в исходном коде Python. u"\u00f8" - единственный символ Юникода, но "\u00f8" в выводе - восемь символов (в исходном коде Python вы можете использовать его как r'"\u00f8"' == '"\\u00f8"' == u'"\\u00f8"' (обратная косая черта является специальной как в литералах Python, так и в тексте json - двойное экранирование может случиться). В JSON нет \x escape-кодов:

>>> 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"'

Вывод json.dumps() - это str, которая является байтовой строкой в ​​Python 2. И, следовательно, не следует избегать символов как \xhh?

json.dumps(obj, ensure_ascii=True) создает только печатные символы ascii, поэтому print repr(json.dumps(u"\xf8")) не будет содержать \xhh экранов, которые используются для представления (repr()) непечатаемых символов (байтов).

\u escape файлы могут понадобиться даже для ввода только ascii:

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

Выход

["\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"]

Но разве это не сбивает с толку, потому что \uxxxx является символом юникода и должен использоваться внутри строки unicode

\uxxxx - это 6 символов, которые могут быть интерпретированы как один символ в некоторых контекстах, например, в исходном коде Python u"\uxxxx" является литералом Python, который создает строку Unicode в памяти с одним символом Unicode. Но если вы видите \uxxxx в json-тексте; это шесть символов, которые могут представлять один символ Юникода, если вы его загрузите (json.loads()).

На этом этапе вы должны понять, почему len(json.loads('"\\\\"')) == 1.

Ответ 3

\u в "\u00f8" на самом деле не является escape-последовательностью, такой как \x. \u является буквальным r'\u'. Но такие байтовые строки могут быть легко преобразованы в Unicode.

Демо:

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)

Выход

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

Как отмечает J.F.Sebastian в комментариях, внутри строки Unicode \u00f8 - это истинный код эвакуации, то есть в строке Python 3 или в строке Python 2 u"\u00f8". Также обратите внимание на его другие замечания!