gpt4 book ai didi

python - pickle 一个包含大型 numpy 数组的对象

转载 作者:行者123 更新时间:2023-12-02 04:22:52 25 4
gpt4 key购买 nike

我正在 pickle 一个具有以下结构的对象:

obj
|---metadata
|---large numpy array

我希望能够访问元数据。但是,如果我 pickle.load() 对象并遍历一个目录(比如说因为我正在寻找一些特定的元数据来确定要返回哪个元数据),那么它就会变得很长。我猜 pickle 想加载整个对象。

有没有办法只访问对象的顶级元数据而不必加载整个东西?

我考虑过维护一个索引,但这意味着我必须实现它的逻辑并使其保持最新状态,如果有更简单的解决方案,我宁愿避免......

最佳答案

是的,普通的 pickle 会加载所有东西。在 Python 3.8 中,新的 Pickle 协议(protocol)允许控制对象的序列化方式并为大部分数据使用侧 channel ,但这主要在进程间通信中使用 pickle 时有用。这将需要为您的对象自定义实现 pickle 。

但是,即使使用较旧的 Python 版本,也可以自定义如何将对象序列化到磁盘。

例如,不是将数组作为对象的普通成员,您可以让它们“生活”在另一个数据结构中——比如字典,并通过该字典间接实现对数组的数据访问。

在 Python 3.8 版中,这将要求您在 pickle-customization 上“作弊”,因为在您的对象序列化时,自定义方法应该将单独的数据保存为副作用。但除此之外,它应该是直截了当的。

更具体地说,当你有类似的东西时:


class MyObject:
def __init__(self, data: NP.NDArray, meta_data: any):
self.data = data
self.meta_data = meta_data

以这种方式增强它 - 你应该仍然可以对你的对象做任何事情,但现在 pickle 只会选择元数据 - 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__ 的内容。 - 仅包含“保险库”对象中真实数据的 key 。

除了允许您序列化分隔文件中的元数据和数据外,它还允许您通过操作“VAULT”来细粒度地处理内存中的数据。

现在,自定义类的 pickle ,以便它将数据单独保存到磁盘,并在读取时检索它。在 Python 3.8 上,这可能可以“在规则内”完成(我会花时间,因为我正在回答这个问题,来看看那个)。对于传统的 pickle ,我们“打破规则”,将额外的数据保存到磁盘并加载它,作为序列化的副作用。

实际上,我只是想到通常自定义 the pickle protocol 直接使用的方法。 ,如 __reduce_ex____setstate__虽然会起作用,但会再次自动从磁盘中解开整个对象。

一种方法是:在序列化时,将完整数据保存在单独的文件中,并创建更多元数据以便可以找到数组文件。在反序列化时,始终只加载元数据 - 并在描述符上方构建一个机制,以便根据需要延迟加载数组。

所以,我们提供了一个 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

当然,这并不完美,并且可能存在极端情况。
我包括了一些保护措施,以防数据文件被移动到另一个目录 - 但我没有对此进行测试。

除此之外,在这里的交互式 session 中使用它们很顺利,
我可以创建一个 MyObject将被 pickle 分离的实例
来自其 data属性,然后将在需要时加载
在 unpickling 上。

至于仅“将内容保存在数据库中”的建议 - 如果对象存在于数据库中,则此处的某些代码也可以与对象一起使用,并且您更喜欢将原始数据放在文件系统上而不是放在数据库上的“blob 列”。

关于python - pickle 一个包含大型 numpy 数组的对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60673345/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com