Да, обычный рассол будет загружать все. В Python 3.8 новый протокол Pickle позволяет управлять тем, как объекты сериализуются, и использовать побочный канал для большой части данных, но это в основном полезно при использовании pickle в межпроцессном взаимодействии. Это потребует индивидуальной реализации протравливания для ваших объектов.
Однако даже в более старых версиях Python можно настроить порядок сериализации объектов на диск.
Например, вместо того, чтобы ваши массивы были обычными членами ваших объектов, вы могли бы «жить» в другой структуре данных - скажем, в словаре и косвенно осуществлять доступ к данным для ваших массивов через этот словарь.
In Python версии 3.8, для этого потребуется «обмануть» настройку pickle, в том смысле, что после сериализации вашего объекта пользовательский метод должен сохранять отдельные данные в качестве побочного эффекта. Но кроме этого, оно должно быть прямым.
Если говорить более конкретно, когда у вас есть что-то вроде:
class MyObject:
def __init__(self, data: NP.NDArray, meta_data: any):
self.data = data
self.meta_data = meta_data
Увеличьте это таким образом - вы все равно должны делать что угодно вы делаете с вашими объектами, но выборка теперь будет только выбирать метаданные - массивы numpy будут «жить» в отдельной структуре данных, которая не будет автоматически сериализована:
from uuid import uuid4
VAULT = dict()
class SeparateSerializationDescriptor:
def __set_name__(self, owner, name):
self.name = name
def __set__(self, instance, value):
id = instance.__dict__[self.name] = str(uuid4())
VAULT[id] = value
def __get__(self, instance, owner):
if instance is None:
return self
return VAULT[instance.__dict__[self.name]]
def __delete__(self, instance):
del VAULT[instance.__dict__[self.name]]
del instance.__dict__[self.name]
class MyObject:
data = SeparateSerializationDescriptor()
def __init__(self, data: NP.NDArray, meta_data: any):
self.data = data
self.meta_data = meta_data
Действительно, это все, что необходимо для настройки доступа к атрибуту: все обычные варианты использования атрибута self.data
будут без проблем извлекать исходный массив numpy - self.data[0:10]
будет просто работать. Но в этот момент pickle будет извлекать содержимое экземпляра __dict__
, который содержит только ключ к реальным данным в объекте «хранилище».
Помимо того, что вы можете сериализовать метаданные и данные в разделенные файлы, он также позволяет вам детализировать данные в памяти, манипулируя «VAULT».
А теперь, измените выбор класса, чтобы он сохранял данные отдельно на диск, и получить его при чтении. На Python 3.8 это, вероятно, может быть сделано «в рамках правил» (я возьму время, так как я отвечаю на это, чтобы взглянуть на это). Для традиционного рассола мы «нарушаем правила», в которых мы сохраняем дополнительные данные на диск, и загружаем их, как побочные эффекты сериализации.
На самом деле, мне просто пришло в голову, что я обычно настраивал методы, используемые напрямую. протокол протравливания , такой как __reduce_ex__
и __setstate__
, пока будет работать, снова автоматически извлечет весь объект с диска.
Путь к go будет следующим: сериализацию, сохраните полные данные в отдельном файле и создайте еще несколько метаданных, чтобы можно было найти файл массива. После десериализации всегда загружайте только метаданные - и встраивайте в дескриптор выше механизм ленивой загрузки массивов по мере необходимости.
Итак, мы предоставляем класс Mixin, и вместо * вместо него должен вызываться метод dump
pickle.dump
- данные записываются в отдельных файлах. Чтобы открепить объект, обычно используйте Python pickle.load
: он получит только «нормальные» атрибуты объекта. Затем метод .load()
объекта может быть вызван явно для загрузки всех массивов, или он будет вызываться автоматически при первом обращении к данным, ленивым образом:
import pathlib
from uuid import uuid4
import pickle
VAULT = dict()
class SeparateSerializationDescriptor:
def __set_name__(self, owner, name):
self.name = name
def __set__(self, instance, value):
id = instance.__dict__[self.name] = str(uuid4())
VAULT[id] = value
def __get__(self, instance, owner):
if instance is None:
return self
try:
return VAULT[instance.__dict__[self.name]]
except KeyError:
# attempt so silently load missing data from disk upon first array access after unpickling:
instance.load()
return VAULT[instance.__dict__[self.name]]
def __delete__(self, instance):
del VAULT[instance.__dict__[self.name]]
del instance.__dict__[self.name]
class SeparateSerializationMixin:
def _iter_descriptors(self, data_dir):
for attr in self.__class__.__dict__.values():
if not isinstance(attr, SeparateSerializationDescriptor):
continue
id = self.__dict__[attr.name]
if not data_dir:
# use saved absolute path instead of passed in folder
data_path = pathlib.Path(self.__dict__[attr.name + "_path"])
else:
data_path = data_dir / (id + ".pickle")
yield attr, id, data_path
def dump(self, file, protocol=None, **kwargs):
data_dir = pathlib.Path(file.name).absolute().parent
# Annotate paths and pickle all numpyarrays into separate files:
for attr, id, data_path in self._iter_descriptors(data_dir):
self.__dict__[attr.name + "_path"] = str(data_path)
pickle.dump(getattr(self, attr.name), data_path.open("wb"), protocol=protocol)
# Pickle the metadata as originally intended:
pickle.dump(self, file, protocol, **kwargs)
def load(self, data_dir=None):
"""Load all saved arrays associated with this object.
if data_dir is not passed, the the absolute path used on picking is used. Otherwise
the files are searched by their name in the given folder
"""
if data_dir:
data_dir = pathlib.Path(data_dir)
for attr, id, data_path in self._iter_descriptors(data_dir):
VAULT[id] = pickle.load(data_path.open("rb"))
def __del__(self):
for attr, id, path in self._iter_descriptors(None):
VAULT.pop(id, None)
try:
super().__del__()
except AttributeError:
pass
class MyObject(SeparateSerializationMixin):
data = SeparateSerializationDescriptor()
def __init__(self, data, meta_data):
self.data = data
self.meta_data = meta_data
Конечно, это не так идеально, и есть, вероятно, угловые случаи. Я включил некоторые меры предосторожности на случай, если файлы данных будут перемещены в другой каталог, но я не проверял это.
Кроме этого, использование этих файлов в интерактивном сеансе здесь прошло гладко, и я смог создать MyObject
экземпляр, который будет замаскирован отдельно от его атрибута data
, который затем будет загружен именно тогда, когда это необходимо при снятии закалки.
Что касается предложения просто "сохранить содержимое в базе данных" - часть кода здесь может также использоваться с вашими объектами, если они находятся в базе данных, и вы предпочитаете, чтобы необработанные данные в файловой системе, а не в «столбце BLOB-объектов» в базе данных.