Серіалізація об'єктів
Часто на практиці виникає ситуація, коли нам необхідно зберегти певну складну структуру даних для подальшого використання, наприклад для передачі по мережі чи в іншу програму.
Серіалізація — процес перетворення будь-якої структури даних у послідовність бітів.
Зворотною до операції серіалізації є операція десеріалізації — відновлення початкового стану структури даних із бітової послідовності.
Існує декілька методів серіалізації даних. Ми розглянемо два з них.
JSON
Для збереження різноманітних даних у текстовому вигляді часто використовують формат JSON, який став стандартом де-факто у веб-застосунках і є досить популярним в інших галузях.
JSON (JavaScript Object Notation, запис об'єктів JavaScript, вимовляється джейсон) — це текстовий формат обміну даними між комп'ютерами.
JSON підтримується усіма сучасними мовами програмування і знайомий багатьом розробникам, що робить його прекрасним вибором для збереження даних для передачі між застосунками, які написано різними мовами.
В Python цей формат підтримується вбудованим модулем json
. Модуль надає інструменти для серіалізації словників, списків, кортежів, символьних рядків, цілих і дійсних чисел, булевих значень та None.
Також можна реалізувати підтримку інших типів даних шляхом розширення класів JSONEncoder
та JSONDecoder
.
Відповідність об'єктів Python і JSON:
Python | JSON |
---|---|
dict | object |
list, tuple | array |
str | string |
int, long, float | number |
True | true |
False | false |
None | null |
Серіалізація
Для серіалізації даних в потік є функція:
json.dump(
obj, fp,
skipkeys=False,
ensure_ascii=True,
check_circular=True,
allow_nan=True,
cls=None,
indent=None,
separators=None,
default=None,
sort_keys=False,
**kw) -
)
Значення параметрів наступні:
Параметр | Значення |
---|---|
obj |
об'єкт, що серіалізується |
fp |
файловий об'єкт |
skipkeys |
Якщо True , то ключі словника не базового типа (str, int, float, bool, None) буде проігноровано замість того, щоб піднімати виняток TypeError |
ensure_ascii |
Якщо True , то усі не-ASCII символи буде екрановано послідовностями \uXXXX , і результатом буде символьний рядок, який міститиме лише ASCII-символи. Якщо False , то рядки будуть представлені як вони є. |
check_circular |
Якщо False , то перевірку циклічних посилань буде пропущено, а такі посилання будуть піднімати виняток OverflowError . |
allow_nan |
Якщо False , то при спробі серіалізувати значення з комою, яке виходить за припустимі межі, буде піднято ValueError (nan , inf , -inf ) у суворій відповідності зі специфікацією JSON, замість того, щоб використовувати еквіваленти з JavaScript (NaN , Infinity , -Infinity ). |
indent |
Якщо більше 0, то масиви і об'єкти в JSON будуть виводитись з цим рівнем відступа. Якщо рівень відступу 0, від'ємний чи "", то замість цього будуть використовуватись нові рядки. Значення за замовчуванням None відображає найбільш компактне представлення. Якщо символьний рядок, то це значення і буде використовуватись як відступ. |
sort_keys |
Якщо True , то ключі результуючого словника буде відсортовано. |
Спробуємо серіалізувати деякий об'єкт Python в файл у форматі JSON:
import json
data = [
{
'name': 'Петро',
'age': 20,
},
{
'name': "Мар'яна",
'age': 19
}
]
with open('data.json', 'w', encoding="UTF-8") as file:
json.dump(data, file, indent=2, ensure_ascii=False)
У текстовий файл data.json
буде записано об'єкт data
.
Об'єкт можна також серіалізувати у символьний рядок за допомогою функції:
json.dumps(obj,
skipkeys=False,
ensure_ascii=True,
check_circular=True,
allow_nan=True,
cls=None,
indent=None,
separators=None,
default=None,
sort_keys=False,
**kw
)
Значення параметрів такі ж самі як у функції dump()
.
Десеріалізація
Для десеріалізації даних з потоку використовують функцію:
json.load(
fp,
cls=None,
object_hook=None,
parse_float=None,
parse_int=None,
parse_constant=None,
object_pairs_hook=None,
**kw
)
Значення параметрів наступні:
Параметр | Значення |
---|---|
fp |
Потік |
object_hook |
Опціональна функція, яка застосовується до результату декододування об'єкта (dict). Буде використано значення яке поверне ця функція, а не отриманий словник. |
object_pairs_hook |
Опціональна функція, яка застосовується до результату декодування об'єкта з певною послідовністю пар ключ/значення. Буде використано результат який повертає ця функція, замість початкового словника. Якщо також задано object_hook , то пріоритет надається object_pairs_hook . |
parse_float |
Якщо визначено, то буде викликано для кожного значення JSON с плаваючою крапкою. За замовчуванням еквівалентно float(num_str) . |
parse_int |
Якщо визначено, то буде викликано для кожного значення JSON числовим значенням. За замовчуванням еквівалентно int(num_str) . |
parse_constant |
Якщо визначено, то буде викликано для наступних рядків: "-Infinity", "Infinity", "NaN". Може бути використано для підняття винятків при виявленлні помилкових чисел JSON. |
Функція повертає десеріалізований об'єкт.
Якщо десеріалізувати JSON не вдається, буде піднято виняток ValueError
.
Для десеріалізації JSON який записано у символьний рядок використовують функцію loads()
.
Ключі у парах ключ/значення в JSON завжди є символьними рядками. Коли словник конвертується в JSON, усі ключі словника приводяться до символьлних рядків. В результаті цього якщо словник спочатку перетворити в JSON, а потім знову в словник, то можна не отримати словник ідентичний початковому. Іншими словами:
loads(dumps(x)) != x
якщо x
має ключі не str
.
>>> import json
>>> data = {1:11, 2:22}
>>> s = json.dumps(data)
>>> s
'{"1": 11, "2": 22}'
>>> data2 = json.loads(s)
>>> data2
{'1': 11, '2': 22}
>>>
Pickle
Модуль pickle
реалізує бінарні протоколи для серіалізації та десеріалізації об'єктів Python.
Pickle підтримує серіалізацію величезної кількості типів даних, включаючи створені користувачем. Багато з них підтримуються автоматично, але в більш складних випадках об'єкт можна здіснити шляхом реалізації певних спеціальних методів. Pickle коректно обробляє посилання на вже серіалізовані об'єкти і серіалізацію рекурсивних типів даних.
На відміну від JSON, який може бути прочитаний практично де завгодно, pickle
є специфічним для Python і не може бути використаним для обміну даними з застосунками, написаними іншими мовами програмування.
Недоліком pickle є його небезпечність: десеріалізовувати можна лише ті дані, які отримано з надійних джерел, інакше є ризик виконання довільного коду!
Функція:
pickle.dump(
obj,
fp,
protocol=None, *,
fix_imports=True
)
серіалізує об'єкт obj
в потік fp
.
Параметр protocol
вказує протокол який використовується для серіалізації. За замовчуванням дорівнює 3 і саме цей протокол рекомендовано для використання в Python 3, хоча в Python 3.4 додано протокол версії 4 з деякими оптимізаціями. У будь-якому випадку серіалізувати і десеріалізувати потрібно з використанням одного й того ж протоколу.
Функція:
load(
file, *,
fix_imports=True,
encoding="ASCII",
errors="strict"
)
десеріалізує дані з потоку, повертає десеріалізований об'єкт.
Також модуль pickle реалізує функції dumps()
та loads()
.
Модуль pickle визначає декілька винятків:
- pickle.PickleError
- pickle.PicklingError — виникли проблеми з серіалізациєю об'єкта
- pickle.UnpicklingError — виникли проблеми з десеріалізациєю об'єкта
Приклад:
"""Приклад серіалізації за допомогою pickle"""
import pickle
class Person(object):
"""Клас, який описує людину"""
def __init__(self, name, age, sibling=None):
"""Параметры:
name - ім'я
age - вік
sibling - брат чи сестра
"""
self.name = name
self.age = age
self.sibling = sibling
def write_data(filename):
"""Функція створенні і запису даних"""
peter = Person('Петро', 20)
mary = Person('Мар\'яна', 21)
peter.sibling = mary # створення циклічних посилань
mary.sibling = peter
# Серіалізація списка об'єктів
with open('people.bin', 'wb') as file: # 'wb' - записуємо бінарний файл!
pickle.dump([peter, mary], file)
def read_data(filename):
"""Функція читання і вивода даних"""
# Десеріалізація
with open(filename, 'rb') as file:
data = pickle.load(file)
print(data)
if __name__ == '__main__':
write_data('people.bin')
read_data('people.bin')