Вопрос или проблема
С помощью JSON или Pickle я могу создать объект и сохранить его следующим образом:
import pickle
thing = Thingy(some_data) # занимает некоторое время...
# ... делаем с thing что-то, а затем сохраняем, так как он в основном одинаков
with open(filename, 'wb') as f:
pickle.dump(thing, f)
И считать его обратно, если файл сериализации существует:
if thingy_file.exists():
with open(filename, 'rb') as f:
thing=pickle.load(f) # Thingy(some_data) восстановлен
thing.updateyourself() # довольно быстро
else:
thing=Thingy(some_data) # занимает некоторое время...
Тем не менее, эта сериализация выполняется внешне к созданию объекта Thingy…
Теперь предположим, что у меня есть какой-то пользовательский объект с внутренними данными, создание которого занимает долгое время:
class Thingy:
def __init__(self, stuff...)
# это занимает некоторое время в первый раз, но может быть мгновенным, если считать обратно
# псевдо'шное:
if self.file.exists():
print(f'Существующий Thingy {self.file} найден...')
# То, что я пытаюсь сделать здесь, это считать из файла сериализации
# данные, составляющие в последний раз, когда ЭТА ИНСТАНСИЯ Thingy существовала
with open(self.file, 'rb') as f:
self=pickle.load(f)
else:
# печально, это займет некоторое время...
# остальная часть кода объекта...
# ЗДЕСЬ мы либо вычислили Thingy, либо считали его обратно в себя
# Экземпляр может быть изменен -- теперь сохраните себя, чтобы быть прочитанным в будущем:
def save_my_own_instance(self):
# теперь self - это этот экземпляр Thingy, который будет сериализован на диск
# магия, о которой я не знаю...
with open(self.file, 'wb') as f:
pickle.dump(my_own_instance_data, f, pickle.HIGHEST_PROTOCOL)
Итак, вопрос: Какой метод должен использовать объект для сериализации, чтобы восстановить свои собственные данные в своем экземпляре? Как насчет сохранения самого себя внутри своего собственного экземпляра?
Некоторые конкретные сведения о том, что такое Thingy
:
from pathlib import Path
p=Path(a_HUGE_tree_of_data_files) # 750k плюс большие изображения
tree_data=Thingy(p)
# первоначальный запуск на p занимает более часа
# дерево меняется очень мало, поэтому обновление Thingy(p)
# занимает всего несколько секунд
# Я не хочу учить пользователя проверять предыдущие запуски
# Это должно происходить автоматически
Одно из решений этого могло бы быть созданием статического метода в Thingy
.
class Thingy:
@staticmethod
def load_instance_from_file_or_create_new(filename, stuff):
if filename.exists():# проверяем, существует ли файл
with open(filename, 'rb') as f:
instance=pickle.load(f)
return instance
return Thingy(stuff)
def __init__(self, stuff):
# создаем экземпляр обычным образом
def __del__(self):
# экземпляр удаляется
with open(self.file, 'wb') as f:
pickle.dump(my_own_instance_data, f, pickle.HIGHEST_PROTOCOL)
который можно использовать как
thing = Thingy.load_instance_from_file_or_create_new("xyz",stuff)
Статический метод загружает экземпляр Thingy
из файла, если файл существует, иначе создает новый экземпляр с предоставленным stuff
.
Вам нужно будет хранить filename
вне Thingy
, если у вас нет значения по умолчанию для filename
.
А метод __del__
автоматически вызывается всякий раз, когда экземпляр уничтожается, поэтому вы можете использовать его для сохранения экземпляра в файл.
Ответ или решение
Как обеспечить само сериализацию объекта в Python
Вопрос о том, как объект может сериализовать и десериализовать сам себя, особенно когда он требует значительных ресурсов для создания, представляет собой интересную задачу для разработчиков. В данном ответе мы рассмотрим, как можно реализовать такую функциональность на примере кастомного класса Thingy
, который может автоматически загружать данные из файла или создавать новое состояние при его отсутствии.
1. Автоматическая загрузка состояния объекта
Можно использовать статический метод для проверки существования сериализованного файла и загрузки из него объекта, если файл доступен. Это позволяет избавиться от необходимости вручную проверять существование файла перед созданием нового объекта.
import pickle
from pathlib import Path
class Thingy:
def __init__(self, stuff, file_name):
self.file = Path(file_name)
if self.file.exists():
print(f'Existing Thingy {self.file} found...')
# Дезериализация
with open(self.file, 'rb') as f:
loaded_instance = pickle.load(f)
self.__dict__.update(loaded_instance.__dict__)
else:
print('Creating a new Thingy instance...')
self.stuff = stuff
# Остальные операции по инициализации
def save_my_own_instance(self):
# Сериализация
with open(self.file, 'wb') as f:
pickle.dump(self, f, pickle.HIGHEST_PROTOCOL)
# Использование
thing = Thingy('some_data', 'thingy_data.pkl')
# Производим операции с объектом
thing.save_my_own_instance() # Сохраняем текущее состояние
2. Метод для сохранения состояния
В методе save_my_own_instance
объект использует его текущее состояние и сохраняет его в файл. Этот метод позволяет пользователю легко сохранять изменения, которые были внесены в объект, чтобы их можно было восстанавливать позже.
3. Использование специального метода и делегирования
Кроме статического метода, можно использовать метод экземпляра для загрузки состояния. Например, можно включить вызов save_my_own_instance
в метод __del__
, который вызовется при уничтожении объекта.
def __del__(self):
self.save_my_own_instance()
Однако следует быть осторожным при использовании этого метода, так как механизмы сборки мусора Python могут не всегда вызывать __del__
как ожидается. Предпочтительнее явно вызывать метод сохранения состояния, когда это необходимо.
4. Обработка состояния
При загрузке состояния с помощью уменьшения self.__dict__
, мы передаем данные из загруженного экземпляра копируя атрибуты, что гарантирует, что все необходимые данные будут доступны в экземпляре.
Заключение
Реализация само-сериализации в классе в Python позволяет сократить время на создание объектов, избегая ненужных вычислений при повторной инициализации. Подход с использованием метода для загрузки и создания новых экземпляров, а также возможности сериализовать себя, делает работу с объектами более оптимизированной и интуитивной. Это особенно полезно в сценариях, когда данные объекта требуют значительных вычислительных ресурсов и время создания объекта должно быть минимизировано.