diff --git a/src/zarr/core/array.py b/src/zarr/core/array.py index e5fc707f0..2849907f9 100644 --- a/src/zarr/core/array.py +++ b/src/zarr/core/array.py @@ -977,9 +977,17 @@ def _iter_chunk_regions( @property def nbytes(self) -> int: """ - The number of bytes that can be stored in this array. + The total number of bytes that can be stored in the chunks of this array. + + Notes + ----- + This value is calculated by multiplying the number of elements in the array and the size + of each element, the latter of which is determined by the dtype of the array. + For this reason, ``nbytes`` will likely be inaccurate for arrays with variable-length + dtypes. It is not possible to determine the size of an array with variable-length elements + from the shape and dtype alone. """ - return self.nchunks * self.dtype.itemsize + return self.size * self.dtype.itemsize async def _get_selection( self, @@ -1429,7 +1437,7 @@ def _info( _order=self.order, _read_only=self.read_only, _store_type=type(self.store_path.store).__name__, - _count_bytes=self.dtype.itemsize * self.size, + _count_bytes=self.nbytes, _count_bytes_stored=count_bytes_stored, _count_chunks_initialized=count_chunks_initialized, **kwargs, @@ -1740,7 +1748,15 @@ def _iter_chunk_coords( @property def nbytes(self) -> int: """ - The number of bytes that can be stored in this array. + The total number of bytes that can be stored in the chunks of this array. + + Notes + ----- + This value is calculated by multiplying the number of elements in the array and the size + of each element, the latter of which is determined by the dtype of the array. + For this reason, ``nbytes`` will likely be inaccurate for arrays with variable-length + dtypes. It is not possible to determine the size of an array with variable-length elements + from the shape and dtype alone. """ return self._async_array.nbytes diff --git a/tests/test_array.py b/tests/test_array.py index 3eb317e50..cf722c738 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -776,3 +776,21 @@ async def test_special_complex_fill_values_roundtrip(fill_value: Any, expected: assert content is not None actual = json.loads(content.to_bytes()) assert actual["fill_value"] == expected + + +@pytest.mark.parametrize("shape", [(1,), (2, 3), (4, 5, 6)]) +@pytest.mark.parametrize("dtype", ["uint8", "float32"]) +@pytest.mark.parametrize("array_type", ["async", "sync"]) +async def test_nbytes( + shape: tuple[int, ...], dtype: str, array_type: Literal["async", "sync"] +) -> None: + """ + Test that the ``nbytes`` attribute of an Array or AsyncArray correctly reports the capacity of + the chunks of that array. + """ + store = MemoryStore() + arr = Array.create(store=store, shape=shape, dtype=dtype, fill_value=0) + if array_type == "async": + assert arr._async_array.nbytes == np.prod(arr.shape) * arr.dtype.itemsize + else: + assert arr.nbytes == np.prod(arr.shape) * arr.dtype.itemsize