Hickle - 基于HDF5的pickle版本
项目描述
Hickle
Hickle是一个HDF5的pickle克隆,但有一个特色:它不是将数据序列化为pickle文件,而是将数据转储到HDF5文件(层次数据格式)。它旨在成为pickle(对于常见数据对象)的“直接替换”,但实际上是一个融合了h5py
和pickle
并具有扩展功能的组合。
也就是说:hickle
是一种将Python变量转储到大多数编程语言都可以读取的HDF5文件中的便捷方法,而不仅仅是Python。Hickle速度快,并允许对数据进行透明压缩(LZF / GZIP)。
为什么使用Hickle?
虽然
hickle
设计成是pickles
(或类似json
)的替代品,但它的工作方式非常不同。它不是进行序列化/json化,而是使用出色的h5py
模块存储数据。使用
hickle
的主要原因有- 它比
pickles
和cPickle
更快。 - 它以HDF5格式存储数据。
- 您可以轻松压缩数据。
不使用
hickle
的主要原因有- 您不想以HDF5格式存储数据。虽然
hickle
可以序列化任意Python对象,但这种功能只是为了方便,您可能更愿意使用pickle模块。 - 您希望将数据转换为人类可读的JSON/YAML,在这种情况下,您应该这样做。
因此,如果您希望数据以HDF5格式存储,或者如果您的序列化过程太慢,请尝试使用
hickle
。由于底层使用h5py
,hickle
特别适合存储大型numpy数组。文档
有关
hickle
的文档可以在telegraphic.github.io/hickle/找到。使用示例
hickle
非常易于使用,并且对于那些之前使用过pickle的人来说应该非常熟悉。简而言之,
hickle
提供两种方法:一个hickle.load
方法,用于加载hickle文件,以及一个hickle.dump
方法,用于将数据写入HDF5。以下是一个完整的示例import os
import hickle as hkl
import numpy as np
# Create a numpy array of data
array_obj = np.ones(32768, dtype='float32')
# Dump to file
hkl.dump(array_obj, 'test.hkl', mode='w')
# Dump data, with compression
hkl.dump(array_obj, 'test_gzip.hkl', mode='w', compression='gzip')
# Compare filesizes
print('uncompressed: %i bytes' % os.path.getsize('test.hkl'))
print('compressed: %i bytes' % os.path.getsize('test_gzip.hkl'))
# Load data
array_hkl = hkl.load('test_gzip.hkl')
# Check the two are the same file
assert array_hkl.dtype == array_obj.dtype
assert np.all((array_hkl, array_obj))
HDF5压缩选项
hickle
比pickles
的一个主要优势是它允许通过将关键字参数传递给h5py
来应用复杂的HDF5功能。因此,您可以做类似以下的事情hkl.dump(array_obj, 'test_lzf.hkl', mode='w', compression='lzf', scaleoffset=0,
chunks=(100, 100), shuffle=True, fletcher32=True)
有关这些关键字的详细解释可以在http://docs.h5py.org/en/latest/high/dataset.html找到,但我们下面会简要介绍。
在HDF5中,数据集以B树的形式存储,这是一种比连续数据块具有速度优势的树数据结构。在B树中,数据被分成
chunks
,这被利用来实现数据集的resizable-datasets
和通过filter pipelines
进行压缩。例如,shuffle
和scaleoffset
会将您的数据移动到改善压缩率,而fletcher32
会计算校验和。这些文件级选项从数据模型中抽象出来。存储自定义对象
hickle
提供了一些选项来存储自定义Python类的对象。派生自内置类、numpy、scipy、pandas和astropy的对象将使用hickle提供的相应加载器进行存储。任何其他类默认情况下将作为二进制pickle字符串存储。从4.x版本开始,hickle
提供了为自定义类定义专用加载函数的可能性,从5.x版本开始,这些可以收集在模块、包和应用特定的加载模块中。class MyClass():
def __init__(self):
self.name = 'MyClass'
self.value = 42
要为
MyClass
创建加载器,需要定义create_MyClass_dataset
和load_MyClass
或MyClassContainer
类。import hdf5
from hickle.helpers import no_compression
def create_MyClass_dataset(py_obj, h_group, name, **kwargs):
"""
py_obj ..... the instance of MyClass to be dumped
h_group .... the h5py.Group py_obj should be dumped into
name ....... the name of the h5py.Dataset or h5py.Group representing py_obj
**kwargs ... the compression keyword arguments passed to hickle.dump
"""
# if content of MyClass can be represented as single matrix, vector or scalar
# values than created a dataset of appropriate size. and either set its shape and
# dtype parameters # to the appropriate size and tyoe . or directly pass the data
# using the data parameter
ds = h_group.create_dataset(name,data = py_obj.value,**kwargs)
## NOTE: if your class represents a scalar using empty tuple for shape
## then kwargs have to be filtered by no_compression
# ds = h_group.create_dataset(name,data = py_obj.value,shape=(),**no_compression(kwargs))
# set additional attributes providing additional specialisation of content
ds.attrs['name'] = py_obj.name
# when done return the new dataset object and an empty tuple or list
return ds,()
def load_Myclass(h_node, base_type, py_obj_type):
"""
h_node ........ the h5py.Dataset object containing the data of MyClass object to restore
base_type ..... byte string naming the loader to be used for restoring MyClass object
py_obj_type ... MyClass class or MyClass subclass object
"""
# py_obj_type should point to MyClass or any of its subclasses
new_instance = py_obj_type()
new_instance.name = h_node.attrs['name']
new_instance.value = h_node[()]
return new_instance
对于必须作为单个
h5py.Dataset
或h5py.Group
对象存储的复杂对象的多个子项的内容进行存储,请使用create_group
方法而不是create_dataset
来定义create_MyClass_dataset
,并定义相应的MyClassContainer
类。import h5py
from hickle.helpers import PyContainer
def create_MyClass_dataset(py_obj, h_group, name, **kwargs):
"""
py_obj ..... the instance of MyClass to be dumped
h_group .... the h5py.Group py_obj should be dumped into
name ....... the name of the h5py.Dataset or h5py.Group representing py_obj
**kwargs ... the compression keyword arguments passed to hickle.dump
"""
ds = h_group.create_group(name)
# set additional attributes providing additional specialisation of content
ds.attrs['name'] = py_obj.name
# when done return the new dataset object and a tuple, list or generator function
# providing for all subitems a tuple or list describing containgin
# name ..... the name to be used storing the subitem within the h5py.Group object
# item ..... the subitem object to be stored
# attrs .... dictionary included in attrs of created h5py.Group or h5py.Dataset
# kwargs ... the kwargs as passed to create_MyClass_dataset function
return ds,(('name',py_obj.name,{},kwargs),('value',py_obj.value,{'the answer':True},kwargs))
class MyClassContainer(PyContainer):
"""
Valid container classes must be derived from hickle.helpers.PyContainer class
"""
def __init__(self,h5_attrs,base_type,object_type):
"""
h5_attrs ...... the attrs dictionary attached to the group representing MyClass
base_type ..... byte string naming the loader to be used for restoring MyClass object
py_obj_type ... MyClass class or MyClass subclass object
"""
# the optional protected _content parameter of the PyContainer __init__
# method can be used to change the data structure used to store
# the subitems passed to the append method of the PyContainer class
# per default it is set to []
super().__init__(h5_attrs,base_type,object_type,_content = dict())
def filter(self,h_parent): # optional overload
"""
generator member functoin which can be overloaded to reorganize subitems
of h_parent h5py.Group before being restored by hickle. Its default
implementation simply yields from h_parent.items().
"""
yield from super().filter(h_parent)
def append(self,name,item,h5_attrs): # optional overload
"""
in case _content parameter was explicitly set or subitems should be sored
in specific order or have to be preprocessed before the next item is appended
than this can be done before storing in self._content.
name ....... the name identifying subitem item within the parent hdf5.Group
item ....... the object representing the subitem
h5_attrs ... attrs dictionary attached to h5py.Dataset, h5py.Group representing item
"""
self._content[name] = item
def convert(self):
"""
called by hickle when all sub items have been appended to MyClass PyContainer
this method must be implemented by MyClass PyContainer.
"""
# py_obj_type should point to MyClass or any of its subclasses
new_instance = py_obj_type()
new_instance.__dict__.update(self._content)
return new_instance
最后一步是将MyClass的加载器注册到hickle中。这通过调用
hickle.lookup.LoaderManager.register_class
方法完成。from hickle.lookup import LoaderManager
# to register loader for object mapped to h5py.Dataset use
LoaderManager.register_class(
MyClass, # MyClass type object this loader handles
b'MyClass', # byte string representing the name of the loader
create_MyClass_Dataset, # the create dataset function defined in first example above
load_MyClass, # the load dataset function defined in first example above
None, # usually None
True, # Set to False to force explicit storage of MyClass instances in any case
'custom' # Loader is only used when custom loaders are enabled on calling hickle.dump
)
# to register loader for object mapped to h5py.Group use
LoaderManager.register_class(
MyClass, # MyClass type object this loader handles
b'MyClass', # byte string representing the name of the loader
create_MyClass_Dataset, # the create dataset function defined in first example above
None, # usually None
MyClassContainer # the PyContainer to be used to restore content of MyClass
True, # Set to False to force explicit storage of MyClass instances in any case
None # if set to None loader is enabled unconditionally
)
# NOTE: in case content of MyClass instances may be mapped to h5py.Dataset or h5py.Group dependent upon
# their actual complexity than both types of loaders can be merged into one single
# using one common create_MyClass_dataset functoin and defining load_MyClass function and
# MyClassContainer class
对于复杂的Python模块、包和应用程序,定义多个类以供hickle调用hickle.lookup.LoaderManager.register_class
显式加载和处理,会很快变得繁琐且令人困惑。因此,从hickle 5.x版本开始,hickle提供了一种可能性,即收集模块、包或应用程序中定义的所有类和对象的加载器,并将它们包含在专门的加载器模块中,与您的模块、包和应用程序一起安装。
对于包和应用包,load_MyPackage.py
加载器模块必须存储在包目录的hickle_loaders
目录中(第一个包含init.py文件的目录),并应按照以下结构组织。
from hickle.helpers import PyContainer
## define below all create_MyClass_dataset load_MyClass functions and MyClassContainer classes
## of the loaders serving your module, package, application package or application
....
## the class_register table and the exclude_register table are required
## by hickle to properly load and apply your loaders
## each row in the class register table will corresponds to the parameters
## of LoaderManager.register_class and has to be specified in the same order
## as above
class_register = [
[ MyClass, # MyClass type object this loader handles
b'MyClass', # byte string representing the name of the loader
create_MyClass_Dataset, # the create dataset function defined in first example above
load_MyClass, # the load dataset function defined in first example above
None, # usually None
True, # Set to False to force explicit storage of MyClass instances in any case
'custom' # Loader is only used when custom loaders are enabled on calling hickle.dump
],
[ MyClass, # MyClass type object this loader handles
b'MyClass', # byte string representing the name of the loader
create_MyClass_Dataset, # the create dataset function defined in first example above
None, # usually None
MyClassContainer # the PyContainer to be used to restore content of MyClass
True, # Set to False to force explicit storage of MyClass instances in any case
None # if set to None loader is enabled unconditionally
]
]
# used by hickle 4.x legacy loaders and other special loaders
# usually an empty list
exclude_register = []
对于单文件模块和应用脚本,load_MyModule.py
或load_MyApp.py
文件必须存储在与MyModule.py
或My_App.py
相同的目录中的hickle_loaders
目录内。有关更复杂加载器的更多示例以及如何存储字节数组字符串以便在存储时进行压缩的示例,请参见hickle/loaders/
目录中的默认加载器模块。
注意:在HDF5文件中存储复杂对象
HDF5文件格式旨在高效地存储多个大型矩阵、图像和向量,附加一些元数据,并提供一种方便的方式通过树状结构访问数据。它不像Python pickle格式那样设计用于高效地将内存中的对象结构映射到文件。因此,盲目地存储大量没有组合成单个数据集的微小对象和标量值,会导致HDF5以及hickle创建的文件爆炸。文件大小可能达到几个10 GB,而pickle文件只需要大约100 MB。可以通过create_MyClass_dataset
方法将子项组合成更大的numpy数组或其他可映射到h5py.Datasets
的数据结构来防止这种情况,以及通过load_MyClass
函数和/或MyClassContainer.convert
方法在加载时恢复子项的实际结构。
近期更改
- 2021年12月:发布版本5,支持h5py >= 3.0和numpy >= 1.21
- 2020年6月:对版本4进行重大重构,并移除对Python 2的支持。
- 2018年12月:被Journal of Open-Source Software (JOSS)接受。
- 2018年6月:进行重大重构并支持Python 3。
- 2016年8月:添加了对scipy稀疏矩阵
bsr_matrix
、csr_matrix
和csc_matrix
的支持。
性能比较
Hickle的运行速度比pickle默认设置快得多,并且略快于设置protocol=2
的pickle。
In [1]: import numpy as np
In [2]: x = np.random.random((2000, 2000))
In [3]: import pickle
In [4]: f = open('foo.pkl', 'w')
In [5]: %time pickle.dump(x, f) # slow by default
CPU times: user 2 s, sys: 274 ms, total: 2.27 s
Wall time: 2.74 s
In [6]: f = open('foo.pkl', 'w')
In [7]: %time pickle.dump(x, f, protocol=2) # actually very fast
CPU times: user 18.8 ms, sys: 36 ms, total: 54.8 ms
Wall time: 55.6 ms
In [8]: import hickle
In [9]: f = open('foo.hkl', 'w')
In [10]: %time hickle.dump(x, f) # a bit faster
dumping <type 'numpy.ndarray'> to file <HDF5 file "foo.hkl" (mode r+)>
CPU times: user 764 us, sys: 35.6 ms, total: 36.4 ms
Wall time: 36.2 ms
因此,如果您继续使用pickle,请添加protocol=2
关键字(感谢@mrocklin指出这一点)。
对于存储Python字典的列表,hickle比python json编码器快,但比uJson慢。对于包含64个条目的字典,每个条目都是一个长度为4096的随机数字列表,时间如下
json took 2633.263 ms
uJson took 138.482 ms
hickle took 232.181 ms
应注意的是,这些比较当然是不公平的:存储在HDF5中不会帮助您将某些内容转换为JSON,也不会帮助您序列化字符串。但对于快速存储Python变量的内容,这是一个相当不错的选择。
安装指南
简单方法
通过命令行运行pip install hickle
使用pip
安装。
在Windows 32位上安装
在PyPi上,直到H5PY版本2.10和Python 3.8,都提供了预构建的Python wheels包。任何更新的版本都必须手动构建和安装。
-
通过命令行运行
pip install "h5py==2.10"
使用pip
安装h5py 2.10 -
通过命令行运行
pip install hickle
安装
手动安装
-
您应该安装Python 3.5或更高版本。
-
安装hdf5(官方页面:http://www.hdfgroup.org/ftp/HDF5/current/src/unpacked/release_docs/INSTALL)(二进制下载:https://portal.hdfgroup.org/display/support/Downloads)注意:在Windows 32位系统上安装libhdf5的预构建二进制包1.10.4,这是Windows上支持32位的最新版本
-
安装h5py(官方页面:http://docs.h5py.org/en/latest/build.html)
-
下载
hickle
:通过终端:git clone https://github.com/telegraphic/hickle.git 通过手动下载:访问 https://github.com/telegraphic/hickle 并在右侧找到下载ZIP
文件 -
切换到您下载的
hickle
目录 -
然后在
hickle
目录中运行以下命令:python setup.py install
可选要求
- dill:当使用hickle 3和/或hickle 4生成的文件需要使用hickle >= 5加载时需要,用于开发和测试
- astropy:用于开发和测试
- pandas:用于开发和测试
测试
从源安装后,运行python setup.py test
以检查是否正常工作。
错误 & 贡献
欢迎贡献和错误修复。请查看我们的贡献指南以获取有关如何贡献开发的更多详细信息。
引用hickle
如果您在学术研究中使用hickle
,我们将非常感激您在我们的论文中引用开源软件杂志(JOSS)。
Price et al., (2018). Hickle: A HDF5-based python pickle replacement. Journal of Open Source Software, 3(32), 1115, https://doi.org/10.21105/joss.01115
项目详情
下载文件
下载适合您平台的文件。如果您不确定选择哪个,请了解有关安装软件包的更多信息。
源分布
构建分布
hickle-5.0.3.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 027e51ccad279d168123a244525e6c84bac0db64605b311b0bd349891be0c53e |
|
MD5 | bdcf15003cb2758b9ba3763a382e56e1 |
|
BLAKE2b-256 | 哈希值:b14209a543153c1609b656f5c8e33210d6bdd1b7045c0d6e33bc96bb9afbb704 |