From 3f0e9fde756f99b51c729f6c5a904937137cee5f Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Mon, 24 Nov 2025 12:16:46 +0100 Subject: [PATCH 1/7] normalize the applied slice --- xarray/core/indexing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/indexing.py b/xarray/core/indexing.py index 5d022d0430b..4c315e94023 100644 --- a/xarray/core/indexing.py +++ b/xarray/core/indexing.py @@ -365,7 +365,7 @@ def _index_indexer_1d( return old_indexer if is_full_slice(old_indexer): # shortcut for full slices - return applied_indexer + return normalize_slice(applied_indexer, size) indexer: OuterIndexerType if isinstance(old_indexer, slice): From 6510bacd39353f0b679c5f1eb3575911d41fedc2 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Mon, 24 Nov 2025 12:31:29 +0100 Subject: [PATCH 2/7] tests for `_index_indexer_1d` --- xarray/tests/test_indexing.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/xarray/tests/test_indexing.py b/xarray/tests/test_indexing.py index ffcfd3116f4..aa9cf980109 100644 --- a/xarray/tests/test_indexing.py +++ b/xarray/tests/test_indexing.py @@ -321,6 +321,33 @@ def test_slice_slice_by_array(self, old_slice, array, size): expected = np.arange(size)[old_slice][array] assert_array_equal(actual, expected) + @pytest.mark.parametrize( + ["old_indexer", "indexer", "size", "expected"], + ( + pytest.param( + slice(None), slice(None, 3), 5, slice(0, 3, 1), id="full_slice-slice" + ), + pytest.param( + slice(2, 12, 3), slice(1, 3), 16, slice(5, 11, 3), id="slice_step-slice" + ), + pytest.param( + slice(None), np.arange(2, 4), 5, np.arange(2, 4), id="full_slice-range" + ), + pytest.param( + np.arange(5), slice(1, 3), 7, np.arange(1, 3), id="range-slice" + ), + pytest.param(slice(None), 3, 5, 3, id="full_slice-int"), + pytest.param(slice(None), -2, 6, 4, id="full_slice-negative_int"), + ), + ) + def test_index_indexer_1d(self, old_indexer, indexer, size, expected): + actual = indexing._index_indexer_1d(old_indexer, indexer, size) + + if isinstance(expected, np.ndarray): + np.testing.assert_equal(actual, expected) + else: + assert actual == expected + def test_lazily_indexed_array(self) -> None: original = np.random.rand(10, 20, 30) x = indexing.NumpyIndexingAdapter(original) From 2bfd28f53638e388de6979d39e311a21592b4574 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Mon, 24 Nov 2025 12:31:46 +0100 Subject: [PATCH 3/7] normalize all indexer types --- xarray/core/indexing.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/xarray/core/indexing.py b/xarray/core/indexing.py index 4c315e94023..2a86afc0a98 100644 --- a/xarray/core/indexing.py +++ b/xarray/core/indexing.py @@ -355,6 +355,17 @@ def slice_slice_by_array( return new_indexer +def normalize_indexer(indexer, size): + if isinstance(indexer, slice): + return normalize_slice(indexer, size) + elif isinstance(indexer, np.ndarray): + return normalize_array(indexer, size) + else: + if indexer < 0: + return size + indexer + return indexer + + def _index_indexer_1d( old_indexer: OuterIndexerType, applied_indexer: OuterIndexerType, @@ -365,7 +376,7 @@ def _index_indexer_1d( return old_indexer if is_full_slice(old_indexer): # shortcut for full slices - return normalize_slice(applied_indexer, size) + return normalize_indexer(applied_indexer, size) indexer: OuterIndexerType if isinstance(old_indexer, slice): From fc7039f564b76ceb1cf9df2321efa65550f28687 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Mon, 24 Nov 2025 13:34:52 +0100 Subject: [PATCH 4/7] also check `normalize_indexer` --- xarray/tests/test_indexing.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/xarray/tests/test_indexing.py b/xarray/tests/test_indexing.py index aa9cf980109..7f97b71e4cf 100644 --- a/xarray/tests/test_indexing.py +++ b/xarray/tests/test_indexing.py @@ -275,6 +275,24 @@ def test_read_only_view(self) -> None: class TestLazyArray: + @pytest.mark.parametrize( + ["indexer", "size", "expected"], + ( + (4, 5, 4), + (-1, 3, 2), + (slice(None), 4, slice(0, 4, 1)), + (slice(1, -3), 7, slice(1, 4, 1)), + (np.array([-1, 3, -2]), 5, np.array([4, 3, 3])), + ), + ) + def normalize_indexer(self, indexer, size, expected): + actual = indexing.normalize_indexer(indexer, size) + + if isinstance(expected, np.ndarray): + np.testing.assert_equal(actual, expected) + else: + assert actual == expected + def test_slice_slice(self) -> None: arr = ReturnItem() for size in [100, 99]: From 8366a86523afe9646c71e58bd94489872b9039df Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Mon, 24 Nov 2025 13:39:32 +0100 Subject: [PATCH 5/7] a few more test cases --- xarray/tests/test_indexing.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/xarray/tests/test_indexing.py b/xarray/tests/test_indexing.py index 7f97b71e4cf..6b564c6f032 100644 --- a/xarray/tests/test_indexing.py +++ b/xarray/tests/test_indexing.py @@ -345,17 +345,31 @@ def test_slice_slice_by_array(self, old_slice, array, size): pytest.param( slice(None), slice(None, 3), 5, slice(0, 3, 1), id="full_slice-slice" ), + pytest.param( + slice(None), np.arange(2, 4), 5, np.arange(2, 4), id="full_slice-array" + ), + pytest.param(slice(None), 3, 5, 3, id="full_slice-int"), pytest.param( slice(2, 12, 3), slice(1, 3), 16, slice(5, 11, 3), id="slice_step-slice" ), pytest.param( - slice(None), np.arange(2, 4), 5, np.arange(2, 4), id="full_slice-range" + slice(2, 12, 3), + np.array([1, 3]), + 16, + np.array([5, 11]), + id="slice_step-array", ), pytest.param( - np.arange(5), slice(1, 3), 7, np.arange(1, 3), id="range-slice" + np.arange(5), slice(1, 3), 7, np.arange(1, 3), id="array-slice" ), - pytest.param(slice(None), 3, 5, 3, id="full_slice-int"), - pytest.param(slice(None), -2, 6, 4, id="full_slice-negative_int"), + pytest.param( + np.arange(0, 8, 2), + np.arange(1, 3), + 9, + np.arange(2, 6, 2), + id="array-array", + ), + pytest.param(np.arange(3), 2, 5, 2, id="array-int"), ), ) def test_index_indexer_1d(self, old_indexer, indexer, size, expected): From c09f3c6387aea7d839f2d4076cea48a00b449a7d Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Mon, 24 Nov 2025 13:54:54 +0100 Subject: [PATCH 6/7] what's new entry [skip-ci] --- doc/whats-new.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 677b2194a55..84856d379e4 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -33,7 +33,8 @@ Deprecations Bug Fixes ~~~~~~~~~ - +- Always normalize slices in ``LazilyIndexedArray`` instances (:issue:`10941`, :pull:`10948`). + By `Justus Magin `_. Documentation ~~~~~~~~~~~~~ From 946ff90a698260ca7dae23d42d52f44de559d038 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Mon, 24 Nov 2025 17:14:36 +0100 Subject: [PATCH 7/7] reword --- doc/whats-new.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 84856d379e4..26c889d241e 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -33,7 +33,7 @@ Deprecations Bug Fixes ~~~~~~~~~ -- Always normalize slices in ``LazilyIndexedArray`` instances (:issue:`10941`, :pull:`10948`). +- Always normalize slices when indexing ``LazilyIndexedArray`` instances (:issue:`10941`, :pull:`10948`). By `Justus Magin `_. Documentation