Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Сохранение данных в файл JSON. Соответствие типов данных Python к JSON. Сериализация типов данных не поддерживаемых JSON




Сохранение данных в файл JSON

JSON выглядит удивительно похожим на структуру данных, которую вы могли бы определить в ручную в JavaScript. Это не случайно, вы действительно можете использовать функцию eval() из JavaScript чтобы «декодировать» данные сериализованные в json. (Обычные протесты против не доверенного ввода принимаются, но дело в том, что json это корректный JavaScript). По существу, JSON может быть уже хорошо знаком вам.

> > > shell
1
> > > basic_entry = {} ①
> > > basic_entry['id'] = 256
> > > basic_entry['title'] = 'Dive into history, 2009 edition'
> > > basic_entry['tags'] = ('diveintopython', 'docbook', 'html')
> > > basic_entry['published'] = True
> > > basic_entry['comments_link'] = None
> > > import json
> > > with open('basic. json', mode='w', encoding='utf-8') as f: ②
... json. dump(basic_entry, f) ③

① Мы собираемся создать новую структуру данных вместо того чтобы использовать уже имеющуюся структуру данных entry. Позже в этой главе мы увидим что случится, когда мы попробуем кодировать более общую структуру данных в JSON.

② JSON это текстовый формат, это значит, что вы должны открыть файл в текстовом режиме и указать кодировку. Вы никогда не ошибетесь, используя UTF-8.

③ Как и модуль pickle, модуль json определяет функцию dump() которая принимает на вход структуру данных Python и поток для записи. Функция dump() сериализует структуру данных Python и записывает ее в объект потока. Раз мы делаем это в конструкции with, мы можем быть уверенными, что файл будет корректно закрыт, когда мы завершим работу с ним.

Ну и как выглядит результат сериализации в формат json?

you@localhost: ~/diveintopython3/examples$ cat basic. json
{" published": true, " tags": [" diveintopython", " docbook", " html" ], " comments_link": null,
" id": 256, " title": " Dive into history, 2009 edition" }

Это несомненно, намного более читаемо, чем файл pickle. Но json может содержать произвольное количество пробелов между значениями, и модуль json предоставляет простой путь для создания еще более читаемого файла json.

> > > shell
1
> > > with open('basic-pretty. json', mode='w', encoding='utf-8') as f:
... json. dump(basic_entry, f, indent=2) ①

① Если вы передадите параметр ident функции Json. dump() она сделает результирующий файл json более читаемым в ущерб размеру файла. Параметр ident это целое число. 0 значит «расположить каждое значение на отдельной строке». Число больше 0 значит «расположить каждое значение на отдельной строке, и использовать number пробелов для отступов во вложенных структурах данных».

И вот результат:

you@localhost: ~/diveintopython3/examples$ cat basic-pretty. json
{
" published": true,
" tags": [
" diveintopython",
" docbook",
" html"
],
" comments_link": null,
" id": 256,
" title": " Dive into history, 2009 edition"
}

Соответствие типов данных Python к JSON

Поскольку JSON разрабатывался не только для Python, есть некоторые недочеты в покрытии типов данных Python. Некоторые из них просто различие в названии типов, но есть два важных типа данных Python, которые полностью упущены из виду. Посмотрим, сможете ли вы заметить их:

Пометки JSON PYTHON 3
  object dictionary
  array list
  string string
  integer integer
  real number float
* true True
* false False
* null None
* Текст ячейки Текст ячейки
  • - Все переменные в JavaScript регистрозависимые

Вы заметили что потеряно? Кортежи и байты! В JSON есть тип - массив, который модуль JSON ставит в соответствие типу список в Python, но там нет отдельного типа для " статичных массивов» (кортежей). И также в JSON есть хорошая поддержка строк, но нет поддержки объектов типа bytes или массивов байт.

Сериализация типов данных не поддерживаемых JSON

То что в JSON нет встроенной поддержки типа bytes, не значит что вы не сможете сериализовать объекты типа bytes. Модуль json предоставляет расширяемые хуки для кодирования и декодирования неизвестных типов данных. (Под " неизвестными" я имел в виду " не определенные в json". Очевидно, что модуль json знает о массивах байт, но он создан с учетом ограничений спецификации json). Если вы хотите закодировать тип bytes или другие типы данных, которые json не поддерживает, вам необходимо предоставить особые кодировщики и декодировщики для этих типов данных.

> > > shell
1
> > > entry ①
{'comments_link': None,
'internal_id': b'\xDE\xD5\xB4\xF8',
'title': 'Dive into history, 2009 edition',
'tags': ('diveintopython', 'docbook', 'html'),
'article_link': 'http: //diveintomark. org/archives/2009/03/27/dive-into-history-2009-edition',
'published_date': time. struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1),
'published': True}
> > > import json
> > > with open('entry. json', 'w', encoding='utf-8') as f: ②
... json. dump(entry, f) ③
...
Traceback (most recent call last):
File " < stdin> ", line 5, in < module>
File " C: \Python31\lib\json\__init__. py", line 178, in dump
for chunk in iterable:
File " C: \Python31\lib\json\encoder. py", line 408, in _iterencode
for chunk in _iterencode_dict(o, _current_indent_level):
File " C: \Python31\lib\json\encoder. py", line 382, in _iterencode_dict
for chunk in chunks:
File " C: \Python31\lib\json\encoder. py", line 416, in _iterencode
o = _default(o)
File " C: \Python31\lib\json\encoder. py", line 170, in default
raise TypeError(repr(o) + " is not JSON serializable" )
TypeError: b'\xDE\xD5\xB4\xF8' is not JSON serializable

① Хорошо, настало время вновь обратиться к структуре данных entry. Там есть все: логические значения, пустое значение, строка, кортеж строк, объект типа bytes, и структура хранящая время.

② Я знаю, что говорил это ранее, но повторюсь еще раз: json это текстовый формат. Всегда открывайте файлы json в текстовом режиме с кодировкой utf-8.

③ Чтож... _ЭТО_ не хорошо. Что произошло?

А вот что: функция json. dump() попробовала сериализовать объект bytes b'\xDE\xD5\xB4\xF8', но ей не удалось, потому что в json нет поддержки объектов bytes. Однако, если сохранение таких объектов важно для вас, вы можете определить свой " мини формат сериализации".

def to_json(python_object): ①
if isinstance(python_object, bytes): ②
return {'__class__': 'bytes',
'__value__': list(python_object)} ③
raise TypeError(repr(python_object) + ' is not JSON serializable') ④

① Чтобы определить свой собственный " мини формат сериализации" для тех типов данных, что json не поддерживает из коробки, просто определите функцию, которая принимает объект Python как параметр. Этот объект Python будет именно тем объектом, который функция json. dump() не сможет сериализовать сама - в данном случае это объект bytes b'\xDE\xD5\xB4\xF8'

② Вашей специфичной функции сериализации следует проверять тип объектов Python, которые передала ей функция json. dump(). Это не обязательно, если ваша функция сериализует только один тип данных, но это делает кристально ясным какой случай покрывает данная функция, и делает более простым улучшение функции, если вам понадобится сериализовать больше типов данных позже

③ В данном случае я решил конвертировать объект bytes в словарь. Ключ __class__ будет содержать название оригинального типа данных, а ключ __value__ будет хранить само значение. Конечно, это не может быть объекты типа bytes, поэтому нужно преобразовать его во что-нибудь сериализуемое при помощи json. Объекты типа bytes это просто последовательность чисел, каждое число будет где-то от 0 до 255. Мы можем использовать функцию list() чтобы преобразовать объект bytes в список чисел. Итак b'\xDE\xD5\xB4\xF8' становится [222, 213, 180, 248]. (Посчитайте! Это работает! Байт \xDE в шестнадцатеричной системе это 222 в десятичной, \xD5 это 213, и так далее. )

④ Это строка важна. Структура данных, которую вы сериализуете может содержать типы данных которых нет в json, и которые не обрабатывает ваша функция. В таком случае, ваш обработчик должен raise ошибку TypeError чтобы функция json. dump() узнала, что ваш обработчик не смог распознать тип данных.

Вот оно, больше вам ничего не нужно. Действительно, определенная вами функция обработчик возвращает словарь Python, не строку. Вы не пишите сериализацию в json полностью сами, вы просто делаете конвертацию-в-поддерживаемый-тип-данных. Функция json. dump() сделает остальное за вас.

> > > shell
1
> > > import customserializer ①
> > > with open('entry. json', 'w', encoding='utf-8') as f: ②
... json. dump(entry, f, default=customserializer. to_json) ③
...
Traceback (most recent call last):
File " < stdin> ", line 9, in < module>
json. dump(entry, f, default=customserializer. to_json)
File " C: \Python31\lib\json\__init__. py", line 178, in dump
for chunk in iterable:
File " C: \Python31\lib\json\encoder. py", line 408, in _iterencode
for chunk in _iterencode_dict(o, _current_indent_level):
File " C: \Python31\lib\json\encoder. py", line 382, in _iterencode_dict
for chunk in chunks:
File " C: \Python31\lib\json\encoder. py", line 416, in _iterencode
o = _default(o)
File " /Users/pilgrim/diveintopython3/examples/customserializer. py", line 12, in to_json
raise TypeError(repr(python_object) + ' is not JSON serializable') ④
TypeError: time. struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1) is not JSON serializable

① Модуль customserializer, это то где вы только что определили функцию to_json() в предыдущем примере

② Текстовый режим, utf-8, тра-ля-ля. (Вы забудете! Я иногда забываю! И все работает замечательно, пока в один момент не сломается, и тогда оно начинает ломаться еще театральнее)

③ Это важный кусок: чтобы встроить вашу функцию обработчик преобразования в функцию json. dump() передайте вашу функцию в json. dump() в параметре default. (Ура, все в Python - объект! )

④ Замечательно, это и правда работает. Но посмотрите на исключение. Теперь функция json. dump() больше не жалуется о том, что не может сериализовать объект bytes. Теперь она жалуется о совершенно другом объекте: time. struct_time.

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

import time

def to_json(python_object):
if isinstance(python_object, time. struct_time): ①
return {'__class__': 'time. asctime',
'__value__': time. asctime(python_object)} ②
if isinstance(python_object, bytes):
return {'__class__': 'bytes',
'__value__': list(python_object)}
raise TypeError(repr(python_object) + ' is not JSON serializable')

① Добавляя в уже существующую функцию customserializer. to_json() мы должны проверить, что объект Python(с которым у функции json. dump() проблемы) на самом деле time. struct_time.

② Если так, мы сделаем нечто похожее на конвертацию, что мы делали с объектом bytes: преобразуем объект time. struct_time в словарь который содержит только те типы данных что можно сериализовать в json. В данном случае, простейший путь преобразовать дату в значение которое можно сериализовать в json это преобразовать ее к строке при помощи функции time. asctime(). Функция time. asctime() преобразует отвратительно выглядящую time. struct_time в строку 'Fri Mar 27 22: 20: 42 2009'.

С этими двумя особыми преобразованиями, структура данных entry должна сериализовать полность без каких либо проблем.

> > > shell
1
> > > with open('entry. json', 'w', encoding='utf-8') as f:
... json. dump(entry, f, default=customserializer. to_json)
...

you@localhost: ~/diveintopython3/examples$ ls -l example. json
-rw-r--r-- 1 you you 391 Aug 3 13: 34 entry. json
you@localhost: ~/diveintopython3/examples$ cat example. json
{" published_date": {" __class__": " time. asctime", " __value__": " Fri Mar 27 22: 20: 42 2009" },
" comments_link": null, " internal_id": {" __class__": " bytes", " __value__": [222, 213, 180, 248]},
" tags": [" diveintopython", " docbook", " html" ], " title": " Dive into history, 2009 edition",
" article_link": " http: //diveintomark. org/archives/2009/03/27/dive-into-history-2009-edition",
" published": true}

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...