pip install h5py★Wheels ship their own HDF5 — no system library needed. Python 3.10+.conda install h5pyPreferred if you also need MPI or a specific HDF5 build.h5py.__version__ · h5py.version.hdf5_version★Two versions that matter. Report both in bug reports.h5py.version.infoEverything at once: h5py, HDF5, NumPy, Python, platform.PyPI wheels now build against HDF5 2.03.16Also: h5py now marks itself free-threading compatible — tested, but treat as experimental.HDF5_MPI="ON" pip install --no-binary=h5py h5pyThe only way to get parallel support: build from source against a parallel HDF5.
f = h5py.File("d.h5", "r")★Read-only, must exist. The default since h5py 3.0 — it used to be'a'.h5py.File(f, "r+")★Read/write, must exist. The safe way to modify.h5py.File(f, "w")truncatesCreates, and destroys an existing file without asking.h5py.File(f, "x") # or "w-"★Create, but fail if it already exists. Use this instead of'w'when you can.h5py.File(f, "a")★Read/write, creating if absent. The "just work" mode.with h5py.File(f, "r") as file: …★Always. An unclosed file can leave data unflushed and the file locked.ds = f["x"]; f.close(); ds[0]ValueErrorDatasets die with their file. Read what you need before closing.
h5py.File(f, driver="core", backing_store=False)Entirely in memory. Great for tests and scratch files.driver="ros3"Read directly from S3 over HTTP. Alsosec2(default),stdio,family,fileobj.h5py.File(bytes_io_obj, "r")Any Python file-like object works via thefileobjdriver.libver="latest"★Newer, faster on-disk structures — required for SWMR. Old readers may not cope.rdcc_nbytes=64*1024**2★Chunk cache per dataset, default only 1 MiB. Raising it fixes many "chunked is slow" reports.track_order=True★Preserve insertion order. Without it,keys()comes back alphabetical.locking=FalseDisable HDF5 file locking. See card 26 before you reach for this.userblock_size=512Reserve leading bytes for your own header. Must be a power of 2, ≥ 512.
g = f.create_group("a/b/c")★Intermediate groups are created automatically.f["a/b/c"] · f["a"]["b"]["c"]★Equivalent. Paths behave like a shell — absolute or relative.g = f.require_group("a/b")★Get it, or create it if missing. Idempotent scripts want this.f.keys() · f.values() · f.items()★Groups are mappings. Views, not lists — wrap inlist()to snapshot."temperature" in gMembership test. Cheaper and clearer than catchingKeyError.g.name · g.parent · g.fileFull path, containing group, owning File. Every object knows where it lives.keys() is alphabeticalnot insertionUnless the group was made withtrack_order=True.
f.visit(lambda n: print(n))★Every name below this group, recursively.f.visititems(lambda n, o: print(n, o))★Name and object — the one you usually want.f.visit_links() · f.visititems_links()Visit the links themselves, so soft and external links aren't followed.return non-None to stop earlyThe callback's return value ends the walk and is passed back to you.if isinstance(o, h5py.Dataset): …The standard filter inside avisititemscallback.visit() skips broken linksA soft link to a missing target is silently passed over.
ds.attrs["units"] = "celsius"★Dict-like, on any group or dataset.ds.attrs["units"] · .get() · .keys()★Reads like a dict too.ds.attrs.create("n", 1, dtype="i8")When you need to pin the exact dtype or shape.del ds.attrs["units"]Removes it cleanly.dict(ds.attrs)Snapshot them all in one go.attribute strings read back as strasymmetryBut dataset strings read back asbytes. See diagram 2 — this catches everyone.large arrays in .attrsdon'tAttributes live in the object header. Over ~64 KB they spill and slow everything down.
f["alias"] = f["real/dataset"]★A hard link — two names, one object, no copy.f["soft"] = h5py.SoftLink("/real/dataset")★Like a symlink: stores a path, breaks if the target moves.f["ext"] = h5py.ExternalLink("other.h5", "/data")★Reach into a different file transparently.f.get("soft", getlink=True)Inspect the link itself instead of following it.f.get("x", getclass=True)Returns the class — check for Dataset vs Group without opening it.f.get("ext", elink_mode=…, elink_locking=…)3.16Control how external links are opened: mode, SWMR and locking.hard links can't cross filesAnd deleting the last hard link is what actually removes an object.
del f["old/dataset"]★Unlinks it. The object goes when its last link does.the file does not shrinkalwaysHDF5 rarely reclaims freed space. Runh5repack in.h5 out.h5to compact.f.move("a/x", "b/x")Rename or relocate within the same file.f.copy("a/x", "b/x")★A real copy, including attributes.f.copy(src, other_file, "/dest")Copies across files — the clean way to extract a subtree.f.flush()Push buffers to disk without closing. Essential in long-running writers.
ds = f.create_dataset("x", shape=(100, 50), dtype="f4")★Allocates space; contents start at the fill value.f.create_dataset("x", data=arr)★Shape and dtype are taken from the array.f["x"] = arr★Shorthand for the above. No room for options, though.create_dataset("x", (100,))3.16deprecatedNeitherdatanordtypegiven is deprecated and becomes an error in 4.0. Passdtype='f4'to keep today's default.require_dataset("x", shape, dtype)Create or fetch, checking that an existing one matches.fillvalue=np.nan★What unwritten elements read as. Defaults to zero — often the wrong sentinel.create_dataset("e", data=h5py.Empty("f"))A null dataspace: typed, but holds no elements at all.track_times=FalseDrop timestamps so identical data produces byte-identical files.
ds.shape · ds.dtype · ds.size · ds.ndim★NumPy-style, and none of them touch the data.ds.nbytes★Uncompressed size. Check this beforeds[:].ds.chunks★The chunk shape, orNonefor contiguous storage.ds.compression · ds.compression_optsPlusds.shuffle,ds.fletcher32,ds.scaleoffset.ds.filter_ids · ds.filter_names3.16Exact filter pipeline, including third-party filters the old attributes couldn't name.ds.maxshape★Nonein a position means that axis can grow.ds.name · ds.parent · ds.fileWhere it sits in the tree.ds.is_virtual · ds.virtual_sources()For virtual datasets — see card 23.ds.len() # not len(ds)Use it when the first axis exceeds 232;len()overflows on some platforms.
ds[0:100, :5]★Standard NumPy slicing. Returns a realndarray.ds[:] · ds[...]reads allThe whole dataset into memory. Fine at 10 MB, fatal at 100 GB.ds[5]Indexing the first axis. Returns a NumPy scalar for 1-D data.ds["fieldname"]Read one field of a compound dtype without loading the rest.ds.fields(["a", "b"])[:]The multi-field version, as a lazy view.for row in ds: …Iterates the first axis, one row per read. Convenient, not fast.for sl in ds.iter_chunks(): ds[sl]★Walk the dataset chunk-aligned — the fastest full pass.ds[None] · ds[np.newaxis]unsupportedYou can't add axes while slicing. Reshape after reading.
ds[0:100] = arr★Shapes must broadcast. This is the write.ds[...] = 0Scalars broadcast across the whole selection.ds.write_direct(arr, source_sel, dest_sel)★Write from a slice of an existing array with no temporary copy.ds.read_direct(arr, source_sel, dest_sel)The read twin — fills a buffer you already allocated.writing in "r" modeOSErrorReopen with'r+'or'a'.writes land chunk-at-a-timeA partial-chunk write forces a read-modify-write. Align writes to chunk edges when you can.
ds[[1, 4, 9]]★A list of indices works — and got much faster for long lists.3.16indices must be increasingand uniqueHDF5 selections are sets, so no repeats and no reordering.ds[mask] # bool arrayBoolean masks work, including along just the first axis.ds[[1,2], [3,4]]one axis onlyOnly one axis may use a list. Read a bounding box and index it in NumPy.arr = ds[lo:hi]; arr[fancy]★The general workaround, and usually faster anyway.ds[10:2] → emptySlices with stop < start return empty rather than raising.
chunks=(100, 100)★Storage is split into fixed blocks, each written and read whole.chunks=TrueAuto-chunking. A reasonable guess, but it can't know your access pattern.required for compression, resizing and SWMR★Contiguous datasets support none of the three.aim for 10 KB – 1 MB per chunkToo small and the B-tree dominates; too large and you read data you don't need.shape chunks like your reads★Row-wise access wants(1, ncols); column-wise wants(nrows, 1). See diagram 1.chunk cache defaults to 1 MiBper datasetIf one chunk exceeds it, every access re-reads and re-decompresses. Raiserdcc_nbytes.ds.id.read_direct_chunk(offset)Grab a chunk still compressed — andwrite_direct_chunkto store pre-compressed bytes.
compression="gzip", compression_opts=4★Levels 0–9. Universally readable; 4 is a sane default.compression="lzf"★Much faster, weaker ratio. h5py-specific — other tools may not read it.shuffle=True★Reorders bytes by significance. Nearly free, and often a large gzip win on floats.fletcher32=TruePer-chunk checksum. Cheap insurance against silent corruption.scaleoffset=3Lossy — keeps 3 decimal places. Excellent ratios when you accept the precision loss.compression=32001 # bloscAny registered filter by numeric id.hdf5pluginsupports blosc, zstd, bitshuffle and more.compression without chunksignoredh5py turns on auto-chunking for you — which may not be the shape you wanted.filters run on every chunk touchedReading one element decompresses its whole chunk.
maxshape=(None, 3)★Nonemeans unlimited on that axis. Requires chunking.ds.resize((2000, 3))★Grow (or shrink) to an explicit shape.ds.resize(ds.shape[0] + n, axis=0)★The append idiom: resize, then write into the new tail.ds[-n:] = new_blockFill the space you just made.shrinking discards datano undoAnd the space isn't returned to the file either.resizing one row at a timeslowGrow in chunk-sized steps, not element-sized ones.
dtype="f4" · "i8" · np.float32★Any NumPy dtype spec. HDF5 stores the equivalent native type.dtype=[("x", "f4"), ("id", "i8")]★Compound dtypes become HDF5 compound types — a table in a dataset.ds.astype("f8")[:]★Convert during the read, without a second array in memory.complex → compound (r, i)3.16Still the default. With HDF5 2.0 you can opt into true C99 complex types instead.no bool, no datetime64no native typeStore asint8or enum, and dates as epoch ints or ISO strings.dtype="U10"TypeErrorHDF5 has no wide-character type. h5py refuses rather than fake it — see card 18.
ds[0] → b"hello"bytesDataset strings read as bytes by default. Redesigned in h5py 3.0; 2.x gavestr.ds.asstr()[:]★Read as Pythonstr. The portable choice.ds.astype("T")[:]3.14Read as native NumPy variable-width strings. Needs NumPy 2.0+.ds[:].astype("T")slowConverts after reading, via an object array. Put.astypebefore the slice.h5py.string_dtype()★Variable-length UTF-8 — the sensible default for new files.h5py.string_dtype("ascii", 30)Fixed length, in bytes — and multi-byte characters eat more than one.fixed-length truncates silentlyno warningLonger strings are simply cut. Prefer variable-length unless you need fixed records.ds.asstr("latin-1")[:]When the stored encoding metadata lies. Python error handlers work here too.attrs["k"] → str★Attributes decode tostrautomatically (UTF-8, surrogate-escaped). Datasets don't.
h5py.vlen_dtype(np.int32)★Ragged arrays: each element is its own variable-length 1-D array.h5py.enum_dtype({"RED": 0}, basetype="i")Named integer constants, preserved for other HDF5 readers.h5py.opaque_dtype(np.dtype("V10"))Fixed-size blobs HDF5 won't interpret.attrs["blob"] = np.void(raw_bytes)★The right way to store binary that isn't text; recover with.tobytes().h5py.check_string_dtype(ds.dtype)Returns encoding and length, orNone. Alsocheck_vlen_dtype,check_enum_dtype.f["mytype"] = np.dtype("f8")A committed (named) datatype, shareable across datasets.vlen data can't be compressed wellThe payload lives in a heap outside the chunk, so filters barely touch it.
ref = ds.ref; f[ref]★An object reference — a portable pointer that survives renames.create_dataset("r", shape, dtype=h5py.ref_dtype)Store references as data: an array pointing at other objects.rr = ds.regionref[0:10, 2]A region reference names a selection, not just an object.ds[rr] · ds.regionref.shape(rr)Read through it, or inspect the shape it covers.region ref into a ref_dtype dataset3.16TypeErrorNow raises instead of silently dropping the region information.f[ref].nameResolve a reference back to its path — handy when debugging.
ds.dims[0].label = "time"★Name an axis. Cheap, and enormously helpful later.f["t"].make_scale("seconds")★Promote a 1-D dataset to a dimension scale.ds.dims[0].attach_scale(f["t"])★Bind the coordinate values to that axis.ds.dims[0][0][:]Read the attached scale's values back.ds.dims[0].detach_scale(scale)Andds.dims[0].keys()to list what's attached.this is what netCDF4 and xarray read★Attach scales and your file opens as a labelled dataset elsewhere.
one big read beats many small ones★Every slice is a round trip through the HDF5 library.for i in range(n): total += ds[i]slowestn separate reads. Slice a block and loop in NumPy instead.arr = ds[0:10000]; arr.sum()Read in chunk-aligned blocks, compute in memory.raise rdcc_nbytes above one chunk★The single highest-value tuning knob for chunked reads.np.asarray(ds)Reads everything — same asds[:]. Explicit is better.ds.read_direct(buf)Reuse one buffer across a loop instead of allocating per iteration.match chunk shape to access pattern★The one decision that outweighs every other optimisation.
layout = h5py.VirtualLayout(shape=(4, 100), dtype="f4")★Declare the shape of the array you want.vs = h5py.VirtualSource("run0.h5", "data", shape=(100,))★Point at a real dataset in another file.layout[0] = vsMap the source into a slice of the virtual array.f.create_virtual_dataset("all", layout, fillvalue=np.nan)★The result reads like one ordinary dataset.sources are resolved at read timeUpdate a source file and the virtual dataset reflects it.missing source → fillvaluesilentNo error. Always set a distinctivefillvaluesuch as NaN.relative paths are relative to the VDS fileMove the file without its sources and it quietly returns fill values.
f = h5py.File(p, "w", libver="latest")★SWMR needs the modern format. Create all objects before switching it on.f.swmr_mode = True★One-way switch. After this the structure is frozen.h5py.File(p, "r", swmr=True)★How readers attach while the writer is still going.ds.flush() # writer★Publish what you've written so readers can see it.ds.refresh() # reader★Pick up the writer's changes, including a grownshape.no new groups or datasets after swmr_modeby designOnly existing datasets may be appended to.one writer, many readers — never two writerscorruptionHDF5 has no multi-writer mode outside MPI.
h5py.File(p, "w", driver="mpio", comm=MPI.COMM_WORLD)★The genuine multi-writer path. Needs an MPI-enabled build.every rank must call it★File open, dataset creation and resize are all collective — all ranks or deadlock.with ds.collective: ds[rank] = data★Collective I/O. Usually much faster than independent writes.compression + parallel writesunsupportedFilters can't be applied collectively. Write raw, compress afterwards withh5repack.mpiexec -n 4 python script.pyRequiresmpi4py; h5py wants mpi4py 4.0+ on Python ≤ 3.12.one file per rank + a VDS★Often simpler and faster than true parallel HDF5. See card 23.
OSError: unable to lock filethe classicSomething else has it open for writing — often a dead process or a stale NFS lock.HDF5_USE_FILE_LOCKING=FALSElast resortSilences the error and removes the protection. Only when you're certain nobody is writing.h5py.File(p, locking=False)★Per-file, and far better than the global environment variable.h5py holds one global lockThe HDF5 C library isn't thread-safe, so threads serialise. Use processes for parallelism.an open File across fork()corruptionOpen the file inside each worker, never before the fork.free-threaded Python marked compatible3.16Tested, but the release notes advise caution in critical code.reader started before the writer's flushSees a stale or partial view unless you use SWMR properly.
h5ls -r file.h5★Recursive listing from the command line. First thing to run on an unknown file.h5dump -H file.h5Header only: structure, dtypes, filters, no data.h5repack -f GZIP=4 in.h5 out.h5★Recompress, re-chunk, and reclaim space freed by deletes.h5diff a.h5 b.h5Structural and numeric comparison.pip install hdf5plugin★Adds blosc, zstd, bitshuffle, LZ4. Import it before opening the file.HDFView · myHDF5 · vitablesGUI browsers — myHDF5 runs in the browser with no install.xarray.open_dataset(p, engine="h5netcdf")Labelled arrays over the same file, if you attached dimension scales.kerchunk · h5coroIndex an HDF5 file once, then read it efficiently from cloud object storage.