跳转到主要内容

Hickle - 基于HDF5的pickle版本

项目描述

PyPI - Latest Release PyPI - Python Versions CodeCov - Coverage Status JOSS Status

Hickle

Hickle是一个HDF5的pickle克隆,但有一个特色:它不是将数据序列化为pickle文件,而是将数据转储到HDF5文件(层次数据格式)。它旨在成为pickle(对于常见数据对象)的“直接替换”,但实际上是一个融合了h5pypickle并具有扩展功能的组合。

也就是说:hickle是一种将Python变量转储到大多数编程语言都可以读取的HDF5文件中的便捷方法,而不仅仅是Python。Hickle速度快,并允许对数据进行透明压缩(LZF / GZIP)。

为什么使用Hickle?

虽然

hickle
设计成是
pickles
(或类似
json
)的替代品,但它的工作方式非常不同。它不是进行序列化/json化,而是使用出色的
h5py
模块存储数据。

使用

hickle
的主要原因有

  1. 它比
    pickles
    cPickle
    更快。
  2. 它以HDF5格式存储数据。
  3. 您可以轻松压缩数据。

不使用

hickle
的主要原因有

  1. 您不想以HDF5格式存储数据。虽然
    hickle
    可以序列化任意Python对象,但这种功能只是为了方便,您可能更愿意使用pickle模块。
  2. 您希望将数据转换为人类可读的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.pyload_MyApp.py文件必须存储在与MyModule.pyMy_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_matrixcsr_matrixcsc_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包。任何更新的版本都必须手动构建和安装。

  1. 通过命令行运行pip install "h5py==2.10"使用pip安装h5py 2.10

  2. 通过命令行运行pip install hickle安装

手动安装

  1. 您应该安装Python 3.5或更高版本。

  2. 安装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位的最新版本

  3. 安装h5py(官方页面:http://docs.h5py.org/en/latest/build.html

  4. 下载hickle:通过终端:git clone https://github.com/telegraphic/hickle.git 通过手动下载:访问 https://github.com/telegraphic/hickle 并在右侧找到 下载ZIP 文件

  5. 切换到您下载的hickle目录

  6. 然后在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 (117.6 kB 查看哈希值)

上传时间:

构建分布

hickle-5.0.3-py3-none-any.whl (108.0 kB 查看哈希值)

上传时间: Python 3

支持者

AWSAWS云计算和安全赞助商DatadogDatadog监控FastlyFastlyCDNGoogleGoogle下载分析MicrosoftMicrosoftPSF赞助商PingdomPingdom监控SentrySentry错误日志StatusPageStatusPage状态页面