Python как сохранить объект в файл

Обновлено: 04.07.2024

Эта статья посвящена работе с файлами (вводу/выводу) в Python: открытие, чтение, запись, закрытие и другие операции.

Файлы Python

Файл — это всего лишь набор данных, сохраненный в виде последовательности битов на компьютере. Информация хранится в куче данных (структура данных) и имеет название «имя файла» (filename).

В Python существует два типа файлов:

Текстовые файлы

Это файлы с человекочитаемым содержимым. В них хранятся последовательности символов, которые понимает человек. Блокнот и другие стандартные редакторы умеют читать и редактировать этот тип файлов.

Текст может храниться в двух форматах: ( .txt ) — простой текст и ( .rtf ) — «формат обогащенного текста».

Бинарные файлы

В бинарных файлах данные отображаются в закодированной форме (с использованием только нулей (0) и единиц (1) вместо простых символов). В большинстве случаев это просто последовательности битов.

Они хранятся в формате .bin .

Любую операцию с файлом можно разбить на три крупных этапа:

  1. Открытие файла
  2. Выполнение операции (запись, чтение)
  3. Закрытие файла

Открытие файла

Метод open()

В Python есть встроенная функция open() . С ее помощью можно открыть любой файл на компьютере. Технически Python создает на его основе объект.

  • file_name = имя открываемого файла
  • access_mode = режим открытия файла. Он может быть: для чтения, записи и т. д. По умолчанию используется режим чтения ( r ), если другое не указано. Далее полный список режимов открытия файла

Пример

Создадим текстовый файл example.txt и сохраним его в рабочей директории.

Текстовый файл пример

Следующий код используется для его открытия.

В этом примере f — переменная-указатель на файл example.txt .

Следующий код используется для вывода содержимого файла и информации о нем.

Стоит обратить внимание, что в Windows стандартной кодировкой является cp1252 , а в Linux — utf-08 .

Закрытие файла

Метод close()

После открытия файла в Python его нужно закрыть. Таким образом освобождаются ресурсы и убирается мусор. Python автоматически закрывает файл, когда объект присваивается другому файлу.

Существуют следующие способы:

Способ №1

Проще всего после открытия файла закрыть его, используя метод close() .

После закрытия этот файл нельзя будет использовать до тех пор, пока заново его не открыть.

Способ №2

Также можно написать try/finally , которое гарантирует, что если после открытия файла операции с ним приводят к исключениям, он закроется автоматически.

Без него программа завершается некорректно.

Вот как сделать это исключение:

Файл нужно открыть до инструкции try , потому что если инструкция open сама по себе вызовет ошибку, то файл не будет открываться для последующего закрытия.

Этот метод гарантирует, что если операции над файлом вызовут исключения, то он закроется до того как программа остановится.

Способ №3

Инструкция with

Еще один подход — использовать инструкцию with , которая упрощает обработку исключений с помощью инкапсуляции начальных операций, а также задач по закрытию и очистке.

В таком случае инструкция close не нужна, потому что with автоматически закроет файл.

Вот как это реализовать в коде.

Чтение и запись файлов в Python

В Python файлы можно читать или записывать информацию в них с помощью соответствующих режимов.

Функция read()

Функция read() используется для чтения содержимого файла после открытия его в режиме чтения ( r ).

Синтаксис

  • file = объект файла
  • size = количество символов, которые нужно прочитать. Если не указать, то файл прочитается целиком.

Пример

Функция readline()

Функция readline() используется для построчного чтения содержимого файла. Она используется для крупных файлов. С ее помощью можно получать доступ к любой строке в любой момент.

Пример

Создадим файл test.txt с нескольким строками:

Посмотрим, как функция readline() работает в test.txt .

Обратите внимание, как в последнем случае строки отделены друг от друга.

Функция write()

Функция write() используется для записи в файлы Python, открытые в режиме записи.

Если пытаться открыть файл, которого не существует, в этом режиме, тогда будет создан новый.

Синтаксис

Пример

Предположим, файла xyz.txt не существует. Он будет создан при попытке открыть его в режиме чтения.

Переименование файлов в Python

Функция rename()

Функция rename() используется для переименовывания файлов в Python. Для ее использования сперва нужно импортировать модуль os.

  • src = файл, который нужно переименовать
  • dest = новое имя файла

Пример

Текущая позиция в файлах Python

В Python возможно узнать текущую позицию в файле с помощью функции tell() . Таким же образом можно изменить текущую позицию командой seek() .

Модуль pickle . Сериализация объектов. Примеры использования для записи/чтения информации из бинарных файлов

Содержание

  • 1. Модуль pickle . Сериализация/десериализация объектов. Назначение
  • 2. Использование модуля pickle . Методы dump() , load()
  • 3. Чтение/запись списка, который содержит вещественные числа. Пример
  • 4. Запись/чтение кортежа, содержащего строки. Пример
  • 5. Чтение/запись множества с разнотипными данными. Пример
  • 6. Запись/чтение разнотипных объектов: списка, кортежа и словаря. Пример

Поиск на других ресурсах:

1. Модуль pickle . Сериализация/десериализация объектов. Назначение

Модуль pickle позволяет сохранять в файлах любые объекты Python без использования лишних преобразований. В целом, модуль pickle реализует двоичный протокол для сериализации и десериализации объектов. Под сериализацией понимается преобразование объектов в строку байт. Десериализация предусматривает обратную операцию конвертирования потока байт в исходный объект.

С помощью модуля pickle можно:

  • реализовывать преобразование иерархии объектов в поток байтов (pickling). Потоком байтов может быть, например, файл;
  • выполнять обратное преобразование потока байтов в иерархию объектов (unpickling). Поток байтов может быть получен с двоичного файла или байтового объекта.
2. Использование модуля pickle . Методы dump() , load()

Модуль pickle используется для работы с объектами типа bytes и обеспечивает сериализацию объектов.

Чтобы использовать методы модуля pickle , предварительно его нужно подключить

Чтобы записать объект в файл нужно использовать метод dump() . Простейший вызов метода выглядит следующим образом

  • obj – сохраняемый объект. Этим объектом может быть число, строка, список, кортеж, множество, словарь или другой объект;
  • file – бинарный файл, в котором объект может быть сохранен.

Для чтения объекта из файла используется метод load() . В простейшем случае вызов метода load() следующий

здесь obj – объект, получаемый из файла file .

3. Чтение/запись списка, который содержит вещественные числа. Пример

С помощью методов dump() и load() модуля pickle можно сохранять разнообразные списки в файлах. При этом не нужно делать дополнительных преобразований.

Результат выполнения программы

4. Запись/чтение кортежа, содержащего строки. Пример

Запись и чтение кортежа не отличается от записи и чтения списка или любого иного объекта.

Результат выполнения программы

5. Чтение/запись множества с разнотипными данными. Пример

Результат работы программы

6. Запись/чтение разнотипных объектов: списка, кортежа и словаря. Пример

В данном примере продемонстрированы возможности модуля pickle для сохранения объектов разных типов: списка, кортежа и словаря.


В этом уроке вы узнаете:

  • Что означает сериализация и десериализация объекта
  • Какие модули вы можете использовать для сериализации объектов в Python
  • Какие виды объектов можно сериализовать с помощью pickle модуля Python
  • Как использовать pickle модуль Python для сериализации объектных иерархий
  • Каковы риски при десериализации объекта из ненадежного источника

Сериализация в Python

Сериализация может использоваться в самых разных ситуациях. Одним из наиболее распространенных применений является сохранение состояния нейронной сети после фазы обучения, чтобы вы могли использовать его позже без необходимости повторять обучение.

Python предлагает три различных модуля в стандартной библиотеке, которые позволяют сериализовать и десериализовывать объекты:

Кроме того, Python поддерживает XML , который вы также можете использовать для сериализации объектов.

marshal Модуль является самым старым из трех перечисленных выше. Он существует главным образом для чтения и записи скомпилированного байт-кода модулей Python или .pyc файлов, которые вы получаете, когда интерпретатор импортирует модуль Python. Таким образом, даже если вы можете использовать marshal для сериализации некоторых из ваших объектов, это не рекомендуется.

Есть несколько причин выбрать формат JSON : он удобен для чтения и не зависит от языка и легче XML. С помощью json модуля вы можете сериализовать и десериализовать несколько стандартных типов Python:

Примечание: С этого момента, вы будете видеть термины pickling и unpickling используется для обозначения сериализации и десериализации с Python pickle модуля.

Итак, у вас есть несколько различных способов сериализации и десериализации объектов в Python. Но какой из них вы должны использовать? Короткий ответ: нет единого решения для всех. Все зависит от вашего варианта использования.

Вот три основных руководства для принятия решения, какой подход использовать:

Внутри pickle модуля Python

Модуль Python pickle основном состоит из четырех методов:

  1. pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None)
  2. pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None)
  3. pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict", buffers=None)
  4. pickle.loads(bytes_object, *, fix_imports=True, encoding="ASCII", errors="strict", buffers=None)

Первые два метода используются во время процесса pickling, а два других используются во время unpickling. Единственная разница между dump() и dumps() заключается в том, что первый создает файл, содержащий результат сериализации, а второй возвращает строку.

Чтобы отличить dumps() от dump() , полезно помнить, что s в конце имени функции стоит символ string . То же самое относится и к load() и loads() : первый читает файл, чтобы начать процесс расслоения, а второй работает со строкой.

Рассмотрим следующий пример. Допустим, у вас есть пользовательский класс example_class с несколькими разными атрибутами, каждый из которых имеет свой тип:

  • a_number
  • a_string
  • a_dictionary
  • a_list
  • a_tuple

В приведенном ниже примере показано, как создать экземпляр класса и выбрать экземпляр, чтобы получить простую строку. После выбора класса вы можете изменить значение его атрибутов, не затрагивая выбранную строку. Затем вы можете снять выделенную строку в другой переменной, восстановив точную копию ранее выбранного класса:

В приведенном выше примере вы создаете несколько различных объектов и сериализуете их pickle . Это производит единственную строку с сериализованным результатом:

Процесс pickling завершается правильно, сохраняя весь ваш экземпляр в этой строке: b'\x80\x03c__main__\nexample_class\nq\x00)\x81q\x01.' После завершения процесса травления вы изменяете свой исходный объект, устанавливая атрибут a_dict в None .

Наконец, вы открываете строку для совершенно нового экземпляра. То, что вы получаете, является глубокой копией вашей исходной структуры объекта с момента начала процесса pickling.

Форматы протокола pickle модуля

Как уже упоминалось выше, pickle модуль является специфичным для Python, и результат процесса pickling может быть прочитан только другой программой Python. Но даже если вы работаете с Python, важно знать, что pickle модуль эволюционировал со временем.

Это означает, что если вы выбрали объект с определенной версией Python, вы не сможете удалить его с более старой версией. Совместимость зависит от версии протокола, который вы использовали для процесса травления.

В настоящее время существует шесть различных протоколов, которые pickle может использовать . Чем выше версия протокола, тем более поздним должен быть интерпретатор Python для расщепления.

  1. Протокол версии 0 был первой версией. В отличие от более поздних протоколов, он удобочитаем.
  2. Протокол версии 1 был первым двоичным форматом.
  3. Протокол версии 2 был представлен в Python 2.3.
  4. Протокол версии 3 был добавлен в Python 3.0. Не может быть выбран Python 2.x.
  5. Протокол версии 4 был добавлен в Python 3.4. Он поддерживает более широкий диапазон размеров и типов объектов и является протоколом по умолчанию, начиная с Python 3.8.
  6. Протокол версии 5 был добавлен в Python 3.8. Он имеет поддержку внеполосных данных и улучшенную скорость для внутриполосных данных.

Примечание. Более новые версии протокола предлагают больше функций и улучшений, но ограничены более высокими версиями интерпретатора. Обязательно учитывайте это при выборе протокола для использования.

Чтобы определить самый высокий протокол, который поддерживает ваш интерпретатор, вы можете проверить значение pickle.HIGHEST_PROTOCOL атрибута.

Чтобы выбрать конкретный протокол , необходимо указать версию протокола при вызове load() , loads() , dump() или dumps() . Если вы не укажете протокол, ваш интерпретатор будет использовать версию по умолчанию, указанную в pickle.DEFAULT_PROTOCOL атрибуте.

Выбираемые и необратимые типы

Вы уже узнали, что pickle модуль Python может сериализовать гораздо больше типов, чем json модуль. Тем не менее, не все picklable. Список unpicklable объектов включает соединения с базой данных, открытые сетевые сокеты, запущенные потоки и др.

dill Модуль расширяет возможности pickle . Согласно официальной документации , он позволяет сериализовать менее распространенные типы, такие как функции с выходами, вложенные функции , лямбда-выражения и многие другие.

Чтобы проверить этот модуль, вы можете попробовать выбрать lambda функцию:

Если вы попытаетесь запустить эту программу, вы получите исключение, потому что pickle модуль Python не может сериализовать lambda функцию:

Теперь попробуйте заменить pickle модуль Python на, dill чтобы увидеть, есть ли разница:

Если вы запустите этот код, вы увидите, что dill модуль сериализует lambda без возврата ошибки:

Еще одна интересная особенность dill заключается в том, что он может даже сериализовать весь сеанс интерпретатора. Вот пример:>>>

В этом примере вы запускаете интерпретатор, импортируете модуль и определяете lambda функцию вместе с несколькими другими переменными. Затем вы импортируете dill модуль и вызываете dump_session() для сериализации всей сессии.

Если все идет хорошо, вы должны получить test.pkl файл в текущем каталоге:

Теперь вы можете запустить новый экземпляр интерпретатора и загрузить test.pkl файл для восстановления вашего последнего сеанса:>>>

Первое globals().items() утверждение показывает, что интерпретатор находится в исходном состоянии. Это означает, что вам нужно импортировать dill модуль и вызвать load_session() для восстановления сеанса сериализованного интерпретатора.

Примечание. Прежде чем использовать dill вместо pickle , имейте в виду, что dill не включен в стандартную библиотеку интерпретатора Python и обычно работает медленнее, чем pickle .

Несмотря на то, что он dill позволяет сериализовать более широкий диапазон объектов pickle , он не может решить все проблемы с сериализацией, которые могут у вас возникнуть. Если вам, например, необходимо сериализовать объект, который содержит соединение с базой данных, то у вас непростое время, потому что этот объект даже не для сериализации dill .

Итак, как вы можете решить эту проблему?

Решением в этом случае является исключение объекта из процесса сериализации и повторная инициализация соединения после десериализации объекта.

Вы можете использовать, __getstate__() чтобы определить, что должно быть включено в процесс травления. Этот метод позволяет вам указать, что вы хотите pickle. Если вы не переопределите __getstate__() , __dict__ то будут использованы экземпляры по умолчанию .

В следующем примере вы увидите, как вы можете определить класс с несколькими атрибутами и исключить один атрибут из сериализации с помощью __getstate()__ :

Чтобы решить эту проблему, вы должны указать, что pickle __getstate__() . Сначала вы клонируете весь __dict__ экземпляр, чтобы все атрибуты были определены в классе, а затем вручную удаляете c атрибут unpicklable .

Если вы запустите этот пример и затем десериализуете объект, то увидите, что новый экземпляр не содержит c атрибут:

Но что, если вы захотите выполнить некоторые дополнительные инициализации при снятии травления, скажем, добавив исключенный c объект обратно в десериализованный экземпляр? Вы можете сделать это с помощью __setstate__() :

Передав исключенный c объект в __setstate__() , вы гарантируете, что он появится в __dict__ строке без выделения.

Сжатие Pickled объектов

Хотя pickle формат данных является компактным двоичным представлением структуры объекта, вы все равно можете оптимизировать свою засеченную строку, сжимая ее с помощью bzip2 или gzip .

Чтобы сжать Pickled строку bzip2 , вы можете использовать bz2 модуль из стандартной библиотеки.

В следующем примере вы возьмете строку, выделите ее, а затем сожмете ее с помощью bz2 библиотеки:>>>

При использовании сжатия помните, что файлы меньшего размера создаются за счет более медленного процесса.

Проблемы безопасности с pickle модулем Python

Теперь вы знаете, как использовать pickle модуль для сериализации и десериализации объектов в Python. Процесс сериализации очень удобен, когда вам нужно сохранить состояние вашего объекта на диск или передать его по сети.

Однако есть еще одна вещь, которую вам нужно знать о pickle модуле Python : он небезопасен. Вы помните обсуждение __setstate__() ? Что ж, этот метод отлично подходит для выполнения большей инициализации во время удаления, но он также может быть использован для выполнения произвольного кода в процессе удаления!

Итак, что вы можете сделать, чтобы уменьшить этот риск?

В следующем примере показано, как открепление несанкционированного доступа может раскрыть вашу систему злоумышленникам, даже предоставив им работающую удаленную оболочку:

В этом примере выполняется процесс расщепления __setstate__() , который выполняет команду Bash, чтобы открыть удаленную оболочку для 192.168.1.10 компьютера через порт 8080 .

Вот как вы можете безопасно протестировать этот скрипт на вашем Mac или Linux. Сначала откройте терминал и используйте nc команду для прослушивания соединения с портом 8080:

Это будет терминал атакующего . Если все работает, то команда будет висеть.

Затем откройте другой терминал на том же компьютере (или на любом другом компьютере в сети) и выполните приведенный выше код Python для удаления вредоносного кода. Обязательно измените IP-адрес в коде на IP-адрес вашего атакующего терминала. В моем примере IP-адрес злоумышленника 192.168.1.10 .

Выполнив этот код, жертва предоставит атакующему оболочку:

Если все работает, на атакующей консоли появится оболочка Bash. Эта консоль теперь может работать непосредственно на атакуемой системе:

Итак, позвольте мне повторить эту критическую точку еще раз: не используйте pickle модуль для десериализации объектов из ненадежных источников!

Вывод

Теперь вы знаете, как использовать pickle модуль Python для преобразования иерархии объектов в поток байтов, который можно сохранить на диск или передать по сети. Вы также знаете, что процесс десериализации в Python должен использоваться с осторожностью, поскольку извлечение чего-либо из ненадежного источника может быть чрезвычайно опасным.

В этом уроке вы узнали:

  • Что означает сериализация и десериализация объекта
  • Какие модули вы можете использовать для сериализации объектов в Python
  • Какие виды объектов можно сериализовать с помощью pickle модуля Python
  • Как использовать pickle модуль Python для сериализации объектных иерархий
  • Каковы риски unpickling из ненадежного источника

Обладая этими знаниями, вы хорошо подготовлены для сохранения ваших объектов с помощью pickle модуля Python .


Время от времени требуется сохранить на диск или отослать по сети объект со сложной структурой. Например, текущее состояние нейронной сети, находящейся в процессе обучения. Процесс перевода структуры данных в цепочку битов называется сериализацией.

После прочтения статьи вы будете знать:

  • что такое сериализация и десериализация;
  • как применять эти процессы для собственного удобства;
  • какие существуют встроенные и сторонние библиотеки Python для сериализации;
  • чем отличаются протоколы pickle ;
  • в чём преимущество dill перед pickle ;
  • как с помощью dill сохранить сессию интерпретатора;
  • можно ли сжать сериализованные данные;
  • какие бывают проблемы с безопасностью процесса десериализации.

Итак, сериализация (англ. serialization, marshalling) – это способ преобразования структуры данных в линейную форму, которую можно сохранить или передать по сети. Обратный процесс преобразования сериализованного объекта в исходную структуру данных называется десериализацией (англ. deserialization, unmarshalling).

В стандартной библиотеке Python три модуля позволяют сериализовать и десериализовать объекты:

Кроме того, Python поддерживает XML, который также можно применять для сериализации объектов.

Самый старый модуль из перечисленных – marshal . Он используется для чтения и записи байт-кода модулей Python и .pyc -файлов, создаваемых при импорте модулей Python. Хотя его и можно использовать для сериализации, делать это не рекомендуется.

Модуль json обеспечивает работу со стандартными файлами JSON. Это широко используемый формат обмена данными, удобный для чтения и не зависящий от языка программирования. С помощью модуля json вы можете сериализовать и десериализовать стандартные типы данных Python:

Наконец, ещё один встроенный способ сериализации и десериализации объектов в Python – модуль pickle . Он отличается от модуля json тем, что сериализует объекты в двоичном виде. То есть результат не может быть прочитан человеком. Кроме того, pickle работает быстрее и позволяет сериализовать многие другие типы Python, включая пользовательские.

Модуль pickle содержит четыре основные функции:

  • pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None)
  • pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None)
  • pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict", buffers=None)
  • pickle.loads(bytes_object, *, fix_imports=True, encoding="ASCII", errors="strict", buffers=None)

Первые два метода применяются для сериализации, а два других – для обратного процесса. Разница между первыми двумя методами заключается в том, что dump создаёт файл, содержащий результат сериализации, а dumps – возвращает байтовую строку. То же самое относится к load и loads .

Рассмотрим пример. Допустим, есть пользовательский класс example_class с несколькими атрибутами ( a_number , a_string , a_dictionary , a_list , a_tuple ), каждый из которых имеет свой тип.

В коде ниже показано, как создаётся и сериализуется экземпляр класса. Затем мы изменяем значение внутреннего словаря. Для восстановления исходной структуры можно использовать сохранённый с помощью pickle объект.

Таким образом, pickle создаёт глубокую копию исходной структуры.

Модуль pickle специфичен для Python — результаты сериализации могут быть прочитаны только другой программой на Python. Но даже если вы работаете только с Python, полезно знать, как модуль эволюционировал со временем. От версии протокола зависит совместимость. Сейчас существует 6 версий протоколов:

  • 0 — в отличие от более поздних протоколов, был удобочитаемым.
  • 1 — первый двоичный формат.
  • 2 — представлен в Python 2.3.
  • 3 — добавлен в Python 3.0. Его нельзя выбрать в версиях Python 2.x.
  • 4 — добавлен в Python 3.4, поддерживает более широкий диапазон размеров и типов объектов, и является протоколом по умолчанию с версии 3.8.
  • 5 — добавлен в Python 3.8, имеет поддержку внеполосных данных и улучшает скорость для внутриполосных.
Более новые версии предлагают больше функций и улучшений, но ограничены более высокими версиями интерпретатора. Учитывайте это при выборе протокола. Самый высокий протокол, поддерживаемый интерпретатором, хранится в атрибуте pickle.HIGHEST_PROTOCOL .

Чтобы выбрать конкретный протокол, укажите версию протокола при вызове функции модуля. Иначе будет использоваться версия, соответствующая атрибуту pickle.DEFAULT_PROTOCOL .

Мы уже знаем, что модуль pickle сериализует гораздо больше типов, чем json . Но всё-таки не все. Список несериализуемых с помощью pickle объектов включает соединения с базами данных, открытые сетевые сокеты и действующие потоки. Если вы столкнулись с несериализуемым объектом, есть несколько способов решения проблемы. Первый вариант – использовать стороннюю библиотеку dill .

Модуль dill расширяет возможности pickle . Согласно официальной документации он позволяет сериализовать менее распространённые типы данных, например, вложенные функции (inner functions) и лямбда-выражения. Проверим на примере:

Попытавшись запустить эту программу, мы получим исключение: pickle не может сериализовать лямбда-функцию:

Попробуем заменить pickle на dill (библиотеку можно установить с помощью pip):

Запустим код и увидим, что модуль dill сериализует лямбда-функцию без ошибок:

Ещё одна особенность dill заключается в том, что он умеет сериализовать сеанс интерпретатора:

В этом примере после запуска интерпретатора и ввода нескольких выражений мы импортируем модуль dill и вызываем dump_session() для сериализации сеанса в файле test.pkl в текущем каталоге:

Запустим новый экземпляр интерпретатора и загрузим файл test.pkl для восстановления последнего сеанса:

Прежде чем начать использовать dill вместо pickle , имейте в виду, что dill не включён в стандартную библиотеку Python и обычно работает медленнее, чем pickle .

Модуль dill охватывает гораздо более широкий диапазон объектов, чем pickle , но не решает всех проблем сериализации. К примеру, даже dill не может сериализовать объект, содержащий соединение с базой данных.

В подобных случаях нужно исключить несериализуемый объект из процесса сериализации и повторно инициализировать после десериализации.

Чтобы указать, что должно быть включено в процесс сериализации, нужно использовать метод __getstate__() . Если этот метод не переопределён, будет использоваться дефолтный __dict__() .

В следующем примере показано, как можно определить класс с несколькими атрибутами и исключить один атрибут из сериализации с помощью __getstate__() :

В приведённом примере мы создаём объект с тремя атрибутами. Поскольку один из атрибутов – это лямбда-объект, его нельзя обработать с помощью pickle . Поэтому в __getstate__() мы сначала клонируем весь __dict__ , а затем удаляем несериализуемый атрибут с .

Если мы запустим этот пример, а затем десериализуем объект, то увидим, что новый экземпляр не содержит атрибут c :

Мы также можем выполнить дополнительные инициализации в процессе десериализации. Например, добавить исключённый объект c обратно в десериализованную сущность. Для этого используется метод __setstate__() :

Формат данных pickle является компактным двоичным представлением структуры объекта, но мы всё равно можем её оптимизировать, используя сжатие. Для bzip2-сжатия сериализованной строки можно использовать модуль стандартной библиотеки bz2 :

Процесс сериализации удобен, когда необходимо сохранить состояние объекта на диск или передать по сети. Однако это не всегда безопасно. Как мы обсудили выше, при десериализации объекта в методе __setstate__() может выполняться любой произвольный код. В том числе код злоумышленника.

Простое правило гласит: никогда не десериализуйте данные, поступившие из подозрительного источника или ненадёжной сети. Чтобы предотвратить атаку посредника, используйте модуль стандартной библиотеки hmac для создания подписей и их проверки.

В следующем примере показано, как десериализация файла pickle , присланного злоумышленником, открывает доступ к системе:

В этом примере в процессе распаковки в __setstate__() будет выполнена команда Bash, открывающая удалённую оболочку для компьютера 192.168.1.10 через порт 8080 .

Вы можете протестировать этот скрипт на Mac или Linux, открыв терминал и набрав команду nc для прослушивания порта 8080 :

Это будет терминал атакующего. Затем открываем терминал на том же компьютере (или другом компьютере той же сети) и выполняем приведённый код Python. IP-адрес в коде нужно заменить на IP-адрес атакующего терминала. Выполнив следующую команду, жертва предоставит атакующему доступ:

При запуске скрипта жертвой в терминале злоумышленника оболочка Bash перейдёт в активное состояние:

Эта консоль позволить атакующему работать непосредственно на вашей системе.

Теперь вы знаете, как работать с модулями pickle и dill для преобразования иерархии объектов со сложной структурой в поток байтов. Структуры можно сохранять на диск или передавать в виде байтовой строки по сети. Вы также знаете, что процесс десериализации нужно использовать с осторожностью. Если у вас остались вопросы, задайте их в комментарии под постом.

Читайте также: