Description
Since 3.3.0, passing a chunk size as a numpy integer raises a TypeError. This worked in 3.2.1 and earlier.
The cause is a disagreement between two functions in src/zarr/core/chunk_grids.py about what counts as an integer:
normalize_chunks_nd (line 790) tests isinstance(chunks, numbers.Integral)
normalize_chunks_1d (line 734) tests isinstance(chunks, int)
np.int64 registers as numbers.Integral but is not a subclass of int. So a per-dimension numpy integer passes the outer dispatch, reaches normalize_chunks_1d, fails the isinstance(chunks, int) narrowing, and falls into the else branch intended for explicit per-dimension chunk sequences — where list(chunks) is called on a scalar.
Both lines are still present on main (bd3e398).
Reproduction
import numpy as np
import zarr
zarr.zeros((4, 4), dtype="f4", chunks=(np.int64(2), np.int64(2)))
File "zarr/core/chunk_grids.py", line 742, in normalize_chunks_1d
chunk_list = list(chunks)
^^^^^^^^^^^^
TypeError: 'numpy.int64' object is not iterable
Mixed tuples fail the same way, and only on the numpy-typed entries — (1, 3, np.int64(16), np.int64(16)) raises, (1, 3, 16, 16) does not.
Version info
- zarr 3.3.0 (fails); 3.2.1 (works)
- numpy 2.5.2
- Python 3.12, Linux
Introduced by #3899 (refactor: simplify internal chunk representation), which landed in 3.3.0.
Why this matters in practice
Numpy integers arise naturally whenever a chunk shape is computed rather than written as a literal, since any numpy reduction or elementwise op yields numpy scalars. For example, a downstream library sizing chunks from the array shape:
plane_shape = np.minimum(shape[-2:], 32768) # numpy array
chunks = (1, depth, *plane_shape) # trailing entries are np.int64
This is what we hit: ultrack's large_chunk_size builds its chunk tuple this way and passes it to zarr.zeros, so every array-creation call through it broke on 3.3.0. We are currently pinned to zarr<3.3 as a workaround.
Because the failure is a TypeError about iteration deep inside chunk-grid code, it is quite hard to trace back to "your chunk sizes are numpy integers" — the error names neither chunks nor the offending value.
Suggested fix
Widen the narrowing in normalize_chunks_1d to match its caller:
- if isinstance(chunks, int):
+ if isinstance(chunks, numbers.Integral):
+ chunks = int(chunks)
This is consistent with the rest of the module: guess_chunks already ends with return tuple(int(x) for x in chunks), and the else branch already accepts numbers.Integral elements and coerces them with int(c). Only the scalar path is strict.
Happy to open a PR with the fix plus a regression test covering numpy scalar chunk sizes, if that would be useful.
Description
Since 3.3.0, passing a chunk size as a numpy integer raises a
TypeError. This worked in 3.2.1 and earlier.The cause is a disagreement between two functions in
src/zarr/core/chunk_grids.pyabout what counts as an integer:normalize_chunks_nd(line 790) testsisinstance(chunks, numbers.Integral)normalize_chunks_1d(line 734) testsisinstance(chunks, int)np.int64registers asnumbers.Integralbut is not a subclass ofint. So a per-dimension numpy integer passes the outer dispatch, reachesnormalize_chunks_1d, fails theisinstance(chunks, int)narrowing, and falls into theelsebranch intended for explicit per-dimension chunk sequences — wherelist(chunks)is called on a scalar.Both lines are still present on
main(bd3e398).Reproduction
Mixed tuples fail the same way, and only on the numpy-typed entries —
(1, 3, np.int64(16), np.int64(16))raises,(1, 3, 16, 16)does not.Version info
Introduced by #3899 (
refactor: simplify internal chunk representation), which landed in 3.3.0.Why this matters in practice
Numpy integers arise naturally whenever a chunk shape is computed rather than written as a literal, since any numpy reduction or elementwise op yields numpy scalars. For example, a downstream library sizing chunks from the array shape:
This is what we hit: ultrack's
large_chunk_sizebuilds its chunk tuple this way and passes it tozarr.zeros, so every array-creation call through it broke on 3.3.0. We are currently pinned tozarr<3.3as a workaround.Because the failure is a
TypeErrorabout iteration deep inside chunk-grid code, it is quite hard to trace back to "your chunk sizes are numpy integers" — the error names neither chunks nor the offending value.Suggested fix
Widen the narrowing in
normalize_chunks_1dto match its caller:This is consistent with the rest of the module:
guess_chunksalready ends withreturn tuple(int(x) for x in chunks), and theelsebranch already acceptsnumbers.Integralelements and coerces them withint(c). Only the scalar path is strict.Happy to open a PR with the fix plus a regression test covering numpy scalar chunk sizes, if that would be useful.