I have a bunch of custom classes for which I've implemented a method of saving files in HDF5 format using the h5py
module.
A bit of background: I've accomplished this by first implementing a serialization interface that represents the data in each class as a dictionary containing specific types of data (at the moment, the representations can only contain numpy.ndarray, numpy.int64, numpy.float64, str, and other dictionary instances). The advantage of this limitation is that it puts the dictionaries in data types that are h5py
defaults. I was surprised to find a dearth of code tutorials on recursively saving dictionaries to HDF5 files, so I would really appreciate feedback on my implementation.
Imports:
import numpy as np
import h5py
import os
Saving the data:
def __save_dict_to_hdf5__(cls, dic, filename):
"""
Save a dictionary whose contents are only strings, np.float64, np.int64,
np.ndarray, and other dictionaries following this structure
to an HDF5 file. These are the sorts of dictionaries that are meant
to be produced by the ReportInterface__to_dict__() method.
"""
assert not os.path.exists(filename), 'this is a noclobber operation bud'
with h5py.File(filename, 'w') as h5file:
cls.__recursively_save_dict_contents_to_group__(h5file, '/', dic)
@classmethod
def __recursively_save_dict_contents_to_group__(cls, h5file, path, dic):
"""
Take an already open HDF5 file and insert the contents of a dictionary
at the current path location. Can call itself recursively to fill
out HDF5 files with the contents of a dictionary.
"""
assert type(dic) is types.DictionaryType, "must provide a dictionary"
assert type(path) is types.StringType, "path must be a string"
assert type(h5file) is h5py._hl.files.File, "must be an open h5py file"
for key in dic:
assert type(key) == types.StringType, 'dict keys must be strings to save to hdf5'
if type(dic[key]) in (np.int64, np.float64, types.StringType):
h5file[path + key] = dic[key]
assert h5file[path + key].value == dic[key], 'The data representation in the HDF5 file does not match the original dict.'
if type(dic[key]) is np.ndarray:
h5file[path + key] = dic[key]
assert np.array_equal(h5file[path + key].value, dic[key]), 'The data representation in the HDF5 file does not match the original dict.'
elif type(dic[key]) is types.DictionaryType:
cls.__recursively_save_dict_contents_to_group__(h5file, path + key + '/', dic[key])
Loading the Data:
@classmethod
def __load_dict_from_hdf5__(cls, filename):
"""
Load a dictionary whose contents are only strings, floats, ints,
numpy arrays, and other dictionaries following this structure
from an HDF5 file. These dictionaries can then be used to reconstruct
ReportInterface subclass instances using the
ReportInterface.__from_dict__() method.
"""
with h5py.File(filename, 'r') as h5file:
return cls.__recursively_load_dict_contents_from_group__(h5file, '/')
@classmethod
def __recursively_load_dict_contents_from_group__(cls, h5file, path):
"""
Load contents of an HDF5 group. If further groups are encountered,
treat them like dicts and continue to load them recursively.
"""
ans = {}
for key, item in h5file[path].items():
if type(item) is h5py._hl.dataset.Dataset:
ans[key] = item.value
elif type(item) is h5py._hl.group.Group:
ans[key] = cls.__recursively_load_dict_contents_from_group__(h5file, path + key + '/')
return ans
This passes my unit tests for saving and loading dictionaries with data intact. But I really don't know how Pythonic this is and would appreciate feedback. I tried to leave an HDF5 tag, but it doesn't exist; anyone who is more familiar with the format, and perhaps with h5py
, can maybe tell me if there is a more elegant or idiomatic way to do this (I don't want to confuse the next student who will maintain this), or if I am setting myself up for any nasty surprises.
numpy
coders, and a small number ofh5py
users. I've answered questions on how to store/fetch arrays, but haven't paid any attention to the mapping between dictionaries and groups. \$\endgroup\$h5py
part seems to work fine; I don't see any special issues. I wonder about theclassmethod
stuff. What is the class? Why not just define a set of functions? \$\endgroup\$classmethod
because I need to call__recursively_load_dict_contents_from_group__
recursively, and I don't want to write the name of the class,ReportInterface
, explicitly in the code; this way I have one less thing to break if I change the nameReportInterface
. But perhaps this is a silly concern. \$\endgroup\$