Skip to content

Save number attribute as scalar and not as array? #1445

Open

Description

@paugier

With hdf5, one can save in attributes a number and an array of size 1. These are two different things:

   ATTRIBUTE "a_int" {
      DATATYPE  H5T_STD_I64LE
      DATASPACE  SCALAR
      DATA {
      (0): 10
      }
   }
   ATTRIBUTE "a_list1" {
      DATATYPE  H5T_STD_I64LE
      DATASPACE  SIMPLE { ( 1 ) / ( 1 ) }
      DATA {
      (0): 10
      }
   }

Unfortunately, netcdf4-python saves a number as an array of size 1, so it is not possible to differentiate these two different things and one can easily obtain bad behavior.

This script demonstrates the issue:

#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "h5netcdf",
#     "h5py",
#     "netcdf4",
# ]
# ///

"""
# use netCDF4 (bug)
$ ./bad_behavior.py net

# use h5py (works fine)
$ ./bad_behavior.py
"""

import sys
import subprocess

if len(sys.argv) > 1:
    package = sys.argv[-1]
else:
    package = "h5py"

if package not in ["h5py", "h5netcdf"] and not package.startswith("net"):
    raise ValueError(f"wrong option '{package}'")

if package == "h5py":
    from h5py import File

    path = "data.h5"
elif package == "h5netcdf":
    import h5netcdf

    File = h5netcdf.File
    print(f"h5netcdf {h5netcdf.__version__}")
    path = "data.nc"
else:
    import netCDF4

    File = netCDF4.Dataset
    print(f"netCDF4 {netCDF4.__version__}")
    path = "data_netCDF4.nc"

number = 10

if package.startswith("h5"):
    with File(path, "w") as file:
        file.attrs["a_int"] = number
        file.attrs["a_list1"] = [number]
        file.attrs["a_list2"] = [number, 2 * number]
else:
    with File(path, "w") as file:
        file.a_int = number
        file.a_list1 = [number]
        file.a_list2 = [number, 2 * number]

subprocess.run(["h5dump", str(path)], check=True)

if package.startswith("h5"):
    with File(path, "r") as file:
        attrs = dict(file.attrs)
else:
    with File(path, "r") as file:
        attrs = {name: file.getncattr(name) for name in file.ncattrs()}

print(attrs)

assert len(attrs["a_list2"]) == 2
assert attrs["a_list2"][0] == attrs["a_int"]

# TypeError: object of type 'numpy.int64' has no len()
assert len(attrs["a_list1"]) == 1

# IndexError: invalid index to scalar variable.
assert attrs["a_list1"][0] == attrs["a_int"]

It would be great if numbers could be saved as scalars and lists of size 1 saved as SIMPLE { ( 1 ) / ( 1 ) }.

Related to h5netcdf/h5netcdf#301

CC @kmuehlbauer, @shoyer

Activity

  1. jswhit commented on Nov 7, 2025

    @jswhit
    Collaborator

    netcdf4-python uses nc_put_att from the netcdf-c API to write attributes. There doesn't seem to be any other way to write attributes in netcdf-c.

  2. jswhit2 commented on Nov 10, 2025

    @jswhit2

    This discussion in netcdf-c is relevant. In the last comment @DennisHeimbigner suggests that the netcdf spec requires all attributes to be treated as a vector of values, even for a single element vector. This is consistent with the behavior of the the nc_put_att* functions. The exception seems to be nc_put_att_text which writes a text attribute as a scalar.

  3. ChrisBarker-NOAA commented on Feb 18, 2026

    @ChrisBarker-NOAA
    Contributor

    The exception seems to be nc_put_att_text which writes a text attribute as a scalar.

    which may be because back in the day, there was no string type, only an array of char.

  4. kmuehlbauer commented on Feb 19, 2026

    @kmuehlbauer

    The exception seems to be nc_put_att_text which writes a text attribute as a scalar.

    which may be because back in the day, there was no string type, only an array of char.

    @ChrisBarker-NOAA That might just be the cause. From your experience, do you know of any solutions workarounds to suggest to the OP? It looks like there is no immediate solution to the OP's problem.

    Would you think it might be possible for NetCDF4 to adopt scalar attributes? AFAIK, scalar variables exist, so it is at least not totally off.

  5. jswhit commented on Apr 25, 2026

    @jswhit
    Collaborator

    I don't think we can unless the netcdf-c api provides a way to do it.

  6. ChrisBarker-NOAA commented on Apr 26, 2026

    @ChrisBarker-NOAA
    Contributor

    From that issue on the netCDF-C project:

    I thought we decided that the netcdf-4 spec required all attributes to be treated
    as a vector of values, even if it one element.

    In short -- netCDF-4 is the netcdf API (spec) encoded as HDF5 -- but it is a more restricted data model than HDF5 -- so users should not expect that it can maintain all details of an HDF5 file.

    So it's not just the netcdf-c API, but the spec itself, there is no scalar for numerical attributes in netCDF.

    I can't quickly see what the h5netcdf does in this case from the OP's script, but if it is trying to be netCDF, then it should always use an array of length one, rather than a scalar.

    That is, netCDF4 and h5netcdf should behave identically.

    As I noted, I think the scalar string thing is due to netCDF adding an additional string type, so it has a separate API for strings vs array of char.

    Note that netcdf could add scalar attributes in a future version, but that would be another distinction between the netcdf4 and "classic" data models -- maybe not worth it?

    I've been surprised how many people are still using netCDF3 ....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions