@@ -326,18 +326,23 @@ def as_integer_slice(value):
326
326
327
327
328
328
class IndexCallable :
329
- """Provide getitem syntax for a callable object ."""
329
+ """Provide getitem and setitem syntax for callable objects ."""
330
330
331
- __slots__ = ("func" , )
331
+ __slots__ = ("getter" , "setter" )
332
332
333
- def __init__ (self , func ):
334
- self .func = func
333
+ def __init__ (self , getter , setter = None ):
334
+ self .getter = getter
335
+ self .setter = setter
335
336
336
337
def __getitem__ (self , key ):
337
- return self .func (key )
338
+ return self .getter (key )
338
339
339
340
def __setitem__ (self , key , value ):
340
- raise NotImplementedError
341
+ if self .setter is None :
342
+ raise NotImplementedError (
343
+ "Setting values is not supported for this indexer."
344
+ )
345
+ self .setter (key , value )
341
346
342
347
343
348
class BasicIndexer (ExplicitIndexer ):
@@ -486,10 +491,24 @@ def __array__(self, dtype: np.typing.DTypeLike = None) -> np.ndarray:
486
491
return np .asarray (self .get_duck_array (), dtype = dtype )
487
492
488
493
def _oindex_get (self , key ):
489
- raise NotImplementedError ("This method should be overridden" )
494
+ raise NotImplementedError (
495
+ f"{ self .__class__ .__name__ } ._oindex_get method should be overridden"
496
+ )
490
497
491
498
def _vindex_get (self , key ):
492
- raise NotImplementedError ("This method should be overridden" )
499
+ raise NotImplementedError (
500
+ f"{ self .__class__ .__name__ } ._vindex_get method should be overridden"
501
+ )
502
+
503
+ def _oindex_set (self , key , value ):
504
+ raise NotImplementedError (
505
+ f"{ self .__class__ .__name__ } ._oindex_set method should be overridden"
506
+ )
507
+
508
+ def _vindex_set (self , key , value ):
509
+ raise NotImplementedError (
510
+ f"{ self .__class__ .__name__ } ._vindex_set method should be overridden"
511
+ )
493
512
494
513
def _check_and_raise_if_non_basic_indexer (self , key ):
495
514
if isinstance (key , (VectorizedIndexer , OuterIndexer )):
@@ -500,11 +519,11 @@ def _check_and_raise_if_non_basic_indexer(self, key):
500
519
501
520
@property
502
521
def oindex (self ):
503
- return IndexCallable (self ._oindex_get )
522
+ return IndexCallable (self ._oindex_get , self . _oindex_set )
504
523
505
524
@property
506
525
def vindex (self ):
507
- return IndexCallable (self ._vindex_get )
526
+ return IndexCallable (self ._vindex_get , self . _vindex_set )
508
527
509
528
510
529
class ImplicitToExplicitIndexingAdapter (NDArrayMixin ):
@@ -616,12 +635,18 @@ def __getitem__(self, indexer):
616
635
self ._check_and_raise_if_non_basic_indexer (indexer )
617
636
return type (self )(self .array , self ._updated_key (indexer ))
618
637
638
+ def _vindex_set (self , key , value ):
639
+ raise NotImplementedError (
640
+ "Lazy item assignment with the vectorized indexer is not yet "
641
+ "implemented. Load your data first by .load() or compute()."
642
+ )
643
+
644
+ def _oindex_set (self , key , value ):
645
+ full_key = self ._updated_key (key )
646
+ self .array .oindex [full_key ] = value
647
+
619
648
def __setitem__ (self , key , value ):
620
- if isinstance (key , VectorizedIndexer ):
621
- raise NotImplementedError (
622
- "Lazy item assignment with the vectorized indexer is not yet "
623
- "implemented. Load your data first by .load() or compute()."
624
- )
649
+ self ._check_and_raise_if_non_basic_indexer (key )
625
650
full_key = self ._updated_key (key )
626
651
self .array [full_key ] = value
627
652
@@ -657,7 +682,6 @@ def shape(self) -> tuple[int, ...]:
657
682
return np .broadcast (* self .key .tuple ).shape
658
683
659
684
def get_duck_array (self ):
660
-
661
685
if isinstance (self .array , ExplicitlyIndexedNDArrayMixin ):
662
686
array = apply_indexer (self .array , self .key )
663
687
else :
@@ -739,8 +763,18 @@ def __getitem__(self, key):
739
763
def transpose (self , order ):
740
764
return self .array .transpose (order )
741
765
766
+ def _vindex_set (self , key , value ):
767
+ self ._ensure_copied ()
768
+ self .array .vindex [key ] = value
769
+
770
+ def _oindex_set (self , key , value ):
771
+ self ._ensure_copied ()
772
+ self .array .oindex [key ] = value
773
+
742
774
def __setitem__ (self , key , value ):
775
+ self ._check_and_raise_if_non_basic_indexer (key )
743
776
self ._ensure_copied ()
777
+
744
778
self .array [key ] = value
745
779
746
780
def __deepcopy__ (self , memo ):
@@ -779,7 +813,14 @@ def __getitem__(self, key):
779
813
def transpose (self , order ):
780
814
return self .array .transpose (order )
781
815
816
+ def _vindex_set (self , key , value ):
817
+ self .array .vindex [key ] = value
818
+
819
+ def _oindex_set (self , key , value ):
820
+ self .array .oindex [key ] = value
821
+
782
822
def __setitem__ (self , key , value ):
823
+ self ._check_and_raise_if_non_basic_indexer (key )
783
824
self .array [key ] = value
784
825
785
826
@@ -950,6 +991,16 @@ def apply_indexer(indexable, indexer):
950
991
return indexable [indexer ]
951
992
952
993
994
+ def set_with_indexer (indexable , indexer , value ):
995
+ """Set values in an indexable object using an indexer."""
996
+ if isinstance (indexer , VectorizedIndexer ):
997
+ indexable .vindex [indexer ] = value
998
+ elif isinstance (indexer , OuterIndexer ):
999
+ indexable .oindex [indexer ] = value
1000
+ else :
1001
+ indexable [indexer ] = value
1002
+
1003
+
953
1004
def decompose_indexer (
954
1005
indexer : ExplicitIndexer , shape : tuple [int , ...], indexing_support : IndexingSupport
955
1006
) -> tuple [ExplicitIndexer , ExplicitIndexer ]:
@@ -1399,24 +1450,6 @@ def __init__(self, array):
1399
1450
)
1400
1451
self .array = array
1401
1452
1402
- def _indexing_array_and_key (self , key ):
1403
- if isinstance (key , OuterIndexer ):
1404
- array = self .array
1405
- key = _outer_to_numpy_indexer (key , self .array .shape )
1406
- elif isinstance (key , VectorizedIndexer ):
1407
- array = NumpyVIndexAdapter (self .array )
1408
- key = key .tuple
1409
- elif isinstance (key , BasicIndexer ):
1410
- array = self .array
1411
- # We want 0d slices rather than scalars. This is achieved by
1412
- # appending an ellipsis (see
1413
- # https://numpy.org/doc/stable/reference/arrays.indexing.html#detailed-notes).
1414
- key = key .tuple + (Ellipsis ,)
1415
- else :
1416
- raise TypeError (f"unexpected key type: { type (key )} " )
1417
-
1418
- return array , key
1419
-
1420
1453
def transpose (self , order ):
1421
1454
return self .array .transpose (order )
1422
1455
@@ -1430,22 +1463,43 @@ def _vindex_get(self, key):
1430
1463
1431
1464
def __getitem__ (self , key ):
1432
1465
self ._check_and_raise_if_non_basic_indexer (key )
1433
- array , key = self ._indexing_array_and_key (key )
1466
+
1467
+ array = self .array
1468
+ # We want 0d slices rather than scalars. This is achieved by
1469
+ # appending an ellipsis (see
1470
+ # https://numpy.org/doc/stable/reference/arrays.indexing.html#detailed-notes).
1471
+ key = key .tuple + (Ellipsis ,)
1434
1472
return array [key ]
1435
1473
1436
- def __setitem__ (self , key , value ):
1437
- array , key = self ._indexing_array_and_key (key )
1474
+ def _safe_setitem (self , array , key , value ):
1438
1475
try :
1439
1476
array [key ] = value
1440
- except ValueError :
1477
+ except ValueError as exc :
1441
1478
# More informative exception if read-only view
1442
1479
if not array .flags .writeable and not array .flags .owndata :
1443
1480
raise ValueError (
1444
1481
"Assignment destination is a view. "
1445
1482
"Do you want to .copy() array first?"
1446
1483
)
1447
1484
else :
1448
- raise
1485
+ raise exc
1486
+
1487
+ def _oindex_set (self , key , value ):
1488
+ key = _outer_to_numpy_indexer (key , self .array .shape )
1489
+ self ._safe_setitem (self .array , key , value )
1490
+
1491
+ def _vindex_set (self , key , value ):
1492
+ array = NumpyVIndexAdapter (self .array )
1493
+ self ._safe_setitem (array , key .tuple , value )
1494
+
1495
+ def __setitem__ (self , key , value ):
1496
+ self ._check_and_raise_if_non_basic_indexer (key )
1497
+ array = self .array
1498
+ # We want 0d slices rather than scalars. This is achieved by
1499
+ # appending an ellipsis (see
1500
+ # https://numpy.org/doc/stable/reference/arrays.indexing.html#detailed-notes).
1501
+ key = key .tuple + (Ellipsis ,)
1502
+ self ._safe_setitem (array , key , value )
1449
1503
1450
1504
1451
1505
class NdArrayLikeIndexingAdapter (NumpyIndexingAdapter ):
@@ -1488,13 +1542,15 @@ def __getitem__(self, key):
1488
1542
self ._check_and_raise_if_non_basic_indexer (key )
1489
1543
return self .array [key .tuple ]
1490
1544
1545
+ def _oindex_set (self , key , value ):
1546
+ self .array [key .tuple ] = value
1547
+
1548
+ def _vindex_set (self , key , value ):
1549
+ raise TypeError ("Vectorized indexing is not supported" )
1550
+
1491
1551
def __setitem__ (self , key , value ):
1492
- if isinstance (key , (BasicIndexer , OuterIndexer )):
1493
- self .array [key .tuple ] = value
1494
- elif isinstance (key , VectorizedIndexer ):
1495
- raise TypeError ("Vectorized indexing is not supported" )
1496
- else :
1497
- raise TypeError (f"Unrecognized indexer: { key } " )
1552
+ self ._check_and_raise_if_non_basic_indexer (key )
1553
+ self .array [key .tuple ] = value
1498
1554
1499
1555
def transpose (self , order ):
1500
1556
xp = self .array .__array_namespace__ ()
@@ -1530,19 +1586,20 @@ def __getitem__(self, key):
1530
1586
self ._check_and_raise_if_non_basic_indexer (key )
1531
1587
return self .array [key .tuple ]
1532
1588
1589
+ def _oindex_set (self , key , value ):
1590
+ num_non_slices = sum (0 if isinstance (k , slice ) else 1 for k in key .tuple )
1591
+ if num_non_slices > 1 :
1592
+ raise NotImplementedError (
1593
+ "xarray can't set arrays with multiple " "array indices to dask yet."
1594
+ )
1595
+ self .array [key .tuple ] = value
1596
+
1597
+ def _vindex_set (self , key , value ):
1598
+ self .array .vindex [key .tuple ] = value
1599
+
1533
1600
def __setitem__ (self , key , value ):
1534
- if isinstance (key , BasicIndexer ):
1535
- self .array [key .tuple ] = value
1536
- elif isinstance (key , VectorizedIndexer ):
1537
- self .array .vindex [key .tuple ] = value
1538
- elif isinstance (key , OuterIndexer ):
1539
- num_non_slices = sum (0 if isinstance (k , slice ) else 1 for k in key .tuple )
1540
- if num_non_slices > 1 :
1541
- raise NotImplementedError (
1542
- "xarray can't set arrays with multiple "
1543
- "array indices to dask yet."
1544
- )
1545
- self .array [key .tuple ] = value
1601
+ self ._check_and_raise_if_non_basic_indexer (key )
1602
+ self .array [key .tuple ] = value
1546
1603
1547
1604
def transpose (self , order ):
1548
1605
return self .array .transpose (order )
0 commit comments