Skip to content

Commit 15328b6

Browse files
authored
Declare Dataset, DataArray, Variable, GroupBy unhashable (#8392)
* Add unhashable to generate_ops * Regenerate _typed_ops after adding "unhashable" * Fix variable redefinition The previous commit revealed the following mypy error: xarray/core/dataset.py: note: In member "swap_dims" of class "Dataset": xarray/core/dataset.py:4415: error: Incompatible types in assignment (expression has type "Variable", variable has type "Hashable") [assignment] xarray/core/dataset.py:4415: note: Following member(s) of "Variable" have conflicts: xarray/core/dataset.py:4415: note: __hash__: expected "Callable[[], int]", got "None" xarray/core/dataset.py:4416: error: "Hashable" has no attribute "dims" [attr-defined] xarray/core/dataset.py:4419: error: "Hashable" has no attribute "to_index_variable" [attr-defined] xarray/core/dataset.py:4430: error: "Hashable" has no attribute "to_base_variable" [attr-defined]
1 parent feba698 commit 15328b6

File tree

3 files changed

+43
-15
lines changed

3 files changed

+43
-15
lines changed

xarray/core/_typed_ops.py

+20
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ def __eq__(self, other: DsCompatible) -> Self: # type:ignore[override]
8383
def __ne__(self, other: DsCompatible) -> Self: # type:ignore[override]
8484
return self._binary_op(other, nputils.array_ne)
8585

86+
# When __eq__ is defined but __hash__ is not, then an object is unhashable,
87+
# and it should be declared as follows:
88+
__hash__: None # type:ignore[assignment]
89+
8690
def __radd__(self, other: DsCompatible) -> Self:
8791
return self._binary_op(other, operator.add, reflexive=True)
8892

@@ -291,6 +295,10 @@ def __eq__(self, other: DaCompatible) -> Self: # type:ignore[override]
291295
def __ne__(self, other: DaCompatible) -> Self: # type:ignore[override]
292296
return self._binary_op(other, nputils.array_ne)
293297

298+
# When __eq__ is defined but __hash__ is not, then an object is unhashable,
299+
# and it should be declared as follows:
300+
__hash__: None # type:ignore[assignment]
301+
294302
def __radd__(self, other: DaCompatible) -> Self:
295303
return self._binary_op(other, operator.add, reflexive=True)
296304

@@ -643,6 +651,10 @@ def __ne__(self, other: VarCompatible) -> Self:
643651
def __ne__(self, other: VarCompatible) -> Self | T_DataArray:
644652
return self._binary_op(other, nputils.array_ne)
645653

654+
# When __eq__ is defined but __hash__ is not, then an object is unhashable,
655+
# and it should be declared as follows:
656+
__hash__: None # type:ignore[assignment]
657+
646658
def __radd__(self, other: VarCompatible) -> Self:
647659
return self._binary_op(other, operator.add, reflexive=True)
648660

@@ -851,6 +863,10 @@ def __eq__(self, other: GroupByCompatible) -> Dataset: # type:ignore[override]
851863
def __ne__(self, other: GroupByCompatible) -> Dataset: # type:ignore[override]
852864
return self._binary_op(other, nputils.array_ne)
853865

866+
# When __eq__ is defined but __hash__ is not, then an object is unhashable,
867+
# and it should be declared as follows:
868+
__hash__: None # type:ignore[assignment]
869+
854870
def __radd__(self, other: GroupByCompatible) -> Dataset:
855871
return self._binary_op(other, operator.add, reflexive=True)
856872

@@ -973,6 +989,10 @@ def __eq__(self, other: T_Xarray) -> T_Xarray: # type:ignore[override]
973989
def __ne__(self, other: T_Xarray) -> T_Xarray: # type:ignore[override]
974990
return self._binary_op(other, nputils.array_ne)
975991

992+
# When __eq__ is defined but __hash__ is not, then an object is unhashable,
993+
# and it should be declared as follows:
994+
__hash__: None # type:ignore[assignment]
995+
976996
def __radd__(self, other: T_Xarray) -> T_Xarray:
977997
return self._binary_op(other, operator.add, reflexive=True)
978998

xarray/core/dataset.py

+17-15
Original file line numberDiff line numberDiff line change
@@ -4410,16 +4410,18 @@ def swap_dims(
44104410
# rename_dims() method that only renames dimensions.
44114411

44124412
dims_dict = either_dict_or_kwargs(dims_dict, dims_kwargs, "swap_dims")
4413-
for k, v in dims_dict.items():
4414-
if k not in self.dims:
4413+
for current_name, new_name in dims_dict.items():
4414+
if current_name not in self.dims:
44154415
raise ValueError(
4416-
f"cannot swap from dimension {k!r} because it is "
4416+
f"cannot swap from dimension {current_name!r} because it is "
44174417
f"not one of the dimensions of this dataset {tuple(self.dims)}"
44184418
)
4419-
if v in self.variables and self.variables[v].dims != (k,):
4419+
if new_name in self.variables and self.variables[new_name].dims != (
4420+
current_name,
4421+
):
44204422
raise ValueError(
4421-
f"replacement dimension {v!r} is not a 1D "
4422-
f"variable along the old dimension {k!r}"
4423+
f"replacement dimension {new_name!r} is not a 1D "
4424+
f"variable along the old dimension {current_name!r}"
44234425
)
44244426

44254427
result_dims = {dims_dict.get(dim, dim) for dim in self.dims}
@@ -4429,24 +4431,24 @@ def swap_dims(
44294431

44304432
variables: dict[Hashable, Variable] = {}
44314433
indexes: dict[Hashable, Index] = {}
4432-
for k, v in self.variables.items():
4433-
dims = tuple(dims_dict.get(dim, dim) for dim in v.dims)
4434+
for current_name, current_variable in self.variables.items():
4435+
dims = tuple(dims_dict.get(dim, dim) for dim in current_variable.dims)
44344436
var: Variable
4435-
if k in result_dims:
4436-
var = v.to_index_variable()
4437+
if current_name in result_dims:
4438+
var = current_variable.to_index_variable()
44374439
var.dims = dims
4438-
if k in self._indexes:
4439-
indexes[k] = self._indexes[k]
4440-
variables[k] = var
4440+
if current_name in self._indexes:
4441+
indexes[current_name] = self._indexes[current_name]
4442+
variables[current_name] = var
44414443
else:
44424444
index, index_vars = create_default_index_implicit(var)
44434445
indexes.update({name: index for name in index_vars})
44444446
variables.update(index_vars)
44454447
coord_names.update(index_vars)
44464448
else:
4447-
var = v.to_base_variable()
4449+
var = current_variable.to_base_variable()
44484450
var.dims = dims
4449-
variables[k] = var
4451+
variables[current_name] = var
44504452

44514453
return self._replace_with_new_dims(variables, coord_names, indexes=indexes)
44524454

xarray/util/generate_ops.py

+6
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ def {method}(self) -> Self:
116116
template_other_unary = """
117117
def {method}(self, *args: Any, **kwargs: Any) -> Self:
118118
return self._unary_op({func}, *args, **kwargs)"""
119+
unhashable = """
120+
# When __eq__ is defined but __hash__ is not, then an object is unhashable,
121+
# and it should be declared as follows:
122+
__hash__: None # type:ignore[assignment]"""
119123

120124
# For some methods we override return type `bool` defined by base class `object`.
121125
# We need to add "# type: ignore[override]"
@@ -152,6 +156,7 @@ def binops(
152156
template_binop,
153157
extras | {"type_ignore": _type_ignore(type_ignore_eq)},
154158
),
159+
([(None, None)], unhashable, extras),
155160
(BINOPS_REFLEXIVE, template_reflexive, extras),
156161
]
157162

@@ -185,6 +190,7 @@ def binops_overload(
185190
"overload_type_ignore": _type_ignore(type_ignore_eq),
186191
},
187192
),
193+
([(None, None)], unhashable, extras),
188194
(BINOPS_REFLEXIVE, template_reflexive, extras),
189195
]
190196

0 commit comments

Comments
 (0)