JSON - это текстовый формат сериализации (который, кстати, имеет рекомендованную двоичную кодировку), а не двоичный формат сериализации. Сам модуль json
заботится о кодировании только в той степени, в которой он хотел бы чтобы узнать, какой ужасный тип str
Python 2 должен представлять (это байты ASCII? байты UTF-8? байты латинского-1?).
Поскольку обработка текста Python 2, как заявил, что ужасно, модуль json
с радостью вернет либо str
(когда ensure_ascii
истинно, либо звезды совпадают в других случаях, и он убежден, что вы сказали, что str
совместимо с вашим ожидаемым encoding
и ни один из входных данных на самом деле не является unicode
) или unicode
(когда ensure_ascii
ложно, большую часть времени).
Как и остальные Python 2, sys.stdout
немного хилый Даже если он установлен на encoding='ascii'
в соответствии с настройками вашей локали, он игнорирует его, когда вы записываете в него str
(sys.stdout.write('\xe9')
должен произойти сбой, но вместо этого он обрабатывает str
предварительно кодированные необработанные двоичные данные и не удосуживается проверить, соответствует ли оно ожидаемой кодировке. Но , когда приходит unicode
, у него нет этой опции; unicode
- это text (не текст UTF-8, не текст ASCII и т. д. c.) из идеального текстового мира единорогов и радуг, и этот мир не выражен в taw dry байтах.
Итак sys.stdout
должен кодировать результат, и он делает это с определенной кодировкой локали (sys.stdout.encoding
скажет вам, что это). Когда это ASCII, и он получает что-то, что не может кодировать в ASCII, он взрывается (как и должно быть).
Дело в том, что модуль json
всегда возвращает text (либо unicode
, либо str
, который, как он убежден, фактически является текстом в простонародном мире Py2), и иногда вам повезло, и этот текст оказался в форме Это обходит проверки в sys.stdout
.
Но вы не должны полагаться на это. Если ваш вывод должен быть в заданной кодировке c, используйте эту кодировку . Самый простой (в том смысле, что толкает на себя большую часть работы, которую должен выполнить переводчик) - это не использовать sys.stdout
(явно или неявно через print
) и записывать свои данные в файлы you open с io.open
(обратный порт Python 3 open
, который правильно обрабатывает кодировки), явно указав encoding='utf-8'
. Если вы должны использовать sys.stdout
и настаиваете на игнорировании кодировки локали, вы можете перемотать ее, например:
with io.open(sys.stdout.fileno(), encoding='utf-8', closefd=False) as encodedout:
json.dump(x, encodedout, ensure_ascii=False, encoding="utf-8")
, которая временно оборачивает файловый дескриптор stdout
в современный файловый объект (использование closefd
, чтобы избежать закрытия базового дескриптора, когда он закрыт).
TL; DR: переключиться на Python 3. Python 2 - это ужасно , когда дело доходит до текста не-ASCII, а его модули часто еще хуже (json
должен абсолютно возвращать согласованный тип или, по крайней мере, только один тип для каждого параметра ensure_ascii
, а не динамически выбор на основе входов и encoding
; это даже не самое худшее; модуль csv
хуже). Кроме того, он достиг конца срока службы и не будет исправлен для что-нибудь с этого момента, поэтому его дальнейшее использование делает вас уязвимым для любых проблем безопасности, обнаруженных между началом этого года и концом времени. Среди прочего, Python 3 использует str
исключительно для текста (который имеет полную поддержку Unicode типа Py2 unicode
), а современные Python 3 (3.7+) приведут ASCII-локали к UTF-8 (потому что практически все системы могут справиться с последним), что должно решить все ваши проблемы. Текст не ASCII будет вести себя так же, как текст ASCII, и странные локали, подобные вашей, которые настаивают на том, что они ASCII (и, следовательно, не будут обрабатывать выходные данные не ASCII), будут «исправлены» для работы по вашему желанию, без ручного кодирования и декодирование, перемотка файловых дескрипторов и т. д. c.