json .dump () использует кодировку ASCII c (вместо запрошенного UTF-8) при перенаправлении stdout в файл - PullRequest
2 голосов
/ 25 февраля 2020

Эта крошечная python программа:

#!/usr/bin/env python
# -*- coding: utf8 -*-

import json
import sys

x = { "name":u"This doesn't work β" }

json.dump(x, sys.stdout, ensure_ascii=False, encoding="utf8")
print

Генерирует этот вывод при запуске на терминале:

$ ./tester.py
{"name": "This doesn't work β"}

Это именно то, что я и ожидал. Однако, если я перенаправлю стандартный вывод в файл, произойдет сбой:

$ ./tester.py > output.json
Traceback (most recent call last):
  File "./tester.py", line 9, in <module>
json.dump(x, sys.stdout, ensure_ascii=False, encoding="utf8")
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.py", line 190, in dump
fp.write(chunk)
UnicodeEncodeError: 'ascii' codec can't encode character u'\u03b2' in position 19: ordinal not in range(128)

Однако прямую печать (без json .dump) можно перенаправить в файл:

 print u"This does work β".encode('utf-8')

Как будто пакет json игнорирует параметр кодировка , если stdout не является терминалом.

Как я могу заставить пакет json делать то, что я хочу?

Ответы [ 3 ]

2 голосов
/ 25 февраля 2020

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.

1 голос
/ 25 февраля 2020

Объединение всех комментариев и ответов в один окончательный ответ:

Примечание: этот ответ для Python 2.7. Python 3, вероятно, будет другим.

json spe c говорит, что файлы json кодируются в формате utf-8. Однако пакет Python json не любит рисковать и поэтому записывает прямые ascii и экранирует символы Юникода в выводе.

Вы можете установить флаг sure_ascii в False, в этом случае пакет json будет генерировать вывод Unicode вместо str. В этом случае ваша проблема заключается в кодировании вывода в кодировке Unicode.

Невозможно заставить пакет json генерировать utf-8 или любую другую кодировку на выходе. Это либо ascii, либо unicode; выбирайте.

Аргумент , кодирующий , представлял собой красную сельдь. Эта опция сообщает пакету json, как кодируются строки input .

Вот что в итоге сработало для меня:

ofile = codecs.getwriter('utf-8')(sys.stdout)
json.dump(x, ofile, ensure_ascii=False)

tl; dr: настоящая загадка Именно поэтому он не прервался, просто пропуская стандартный вывод go в терминал. Оказалось, что stdout.write () обнаруживает, когда вывод поступает на терминал, и кодирует переменную среды $ LANG. Когда выходные данные передаются в файл, юникод кодируется в ascii, и возникает ошибка, когда встречается некодируемый символ.

0 голосов
/ 27 февраля 2020

Существует переменная окружения Python, которая может переопределять кодировку для терминала или для перенаправления, поэтому она должна работать без переноса stdout в сценарий.

$ export PYTHONIOENCODING=utf8
$ ./tester.py > output.json
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...