Перейти до змісту

Серіалізація об'єктів

Часто на практиці виникає ситуація, коли нам необхідно зберегти певну складну структуру даних для подальшого використання, наприклад для передачі по мережі чи в іншу програму.

Серіалізація — процес перетворення будь-якої структури даних у послідовність бітів.

Зворотною до операції серіалізації є операція десеріалізації — відновлення початкового стану структури даних із бітової послідовності.

Існує декілька методів серіалізації даних. Ми розглянемо два з них.

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')
Back to top