Skip to content

Commit 7992fb5

Browse files
authored
Alternative Cell.__hash__ implementation for NaN support (#4852)
* Alternative Cell.__hash__ implementation for NaN support * add whatsnew entry
1 parent 28da785 commit 7992fb5

File tree

3 files changed

+46
-11
lines changed

3 files changed

+46
-11
lines changed

docs/src/whatsnew/latest.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ This document explains the changes made to Iris for this release
247247

248248
#. `@rcomer`_ removed some now redundant testing methods. (:pull:`4838`)
249249

250+
#. `@bjlittle`_ extended the GitHub Continuous-Integration to cover testing
251+
on ``py38``, ``py39``, and ``py310``. (:pull:`4840` and :pull:`4852`)
252+
250253

251254
.. comment
252255
Whatsnew author names (@github name) in alphabetical order. Note that,

lib/iris/coords.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1266,7 +1266,13 @@ def _get_2d_coord_bound_grid(bounds):
12661266
return result
12671267

12681268

1269-
class Cell(namedtuple("Cell", ["point", "bound"])):
1269+
class _Hash:
1270+
"""Mutable hash property"""
1271+
1272+
slots = ("_hash",)
1273+
1274+
1275+
class Cell(namedtuple("Cell", ["point", "bound"]), _Hash):
12701276
"""
12711277
An immutable representation of a single cell of a coordinate, including the
12721278
sample point and/or boundary position.
@@ -1326,6 +1332,18 @@ def __new__(cls, point=None, bound=None):
13261332

13271333
return super().__new__(cls, point, bound)
13281334

1335+
def __init__(self, *args, **kwargs):
1336+
# Pre-compute the hash value of this instance at creation time based
1337+
# on the Cell.point alone. This results in a significant performance
1338+
# gain, as Cell.__hash__ is reduced to a minimalist attribute lookup
1339+
# for each invocation.
1340+
try:
1341+
value = 0 if np.isnan(self.point) else hash((self.point,))
1342+
except TypeError:
1343+
# Passing a string to np.isnan causes this exception.
1344+
value = hash((self.point,))
1345+
self._hash = value
1346+
13291347
def __mod__(self, mod):
13301348
point = self.point
13311349
bound = self.bound
@@ -1346,23 +1364,19 @@ def __add__(self, mod):
13461364

13471365
def __hash__(self):
13481366
# Required for >py39 and >np1.22.x due to changes in Cell behaviour for
1349-
# point=np.nan, as calling super().__hash__() returns a different
1350-
# hash and thus does not trigger the following call to Cell.__eq__
1351-
# to determine equality.
1352-
# Note that, no explicit Cell bound nan check is performed here.
1367+
# Cell.point=np.nan, as calling super().__hash__() returns a different
1368+
# hash each time and thus does not trigger the following call to
1369+
# Cell.__eq__ to determine equality.
1370+
# Note that, no explicit Cell.bound nan check is performed here.
13531371
# That is delegated to Cell.__eq__ instead. It's imperative we keep
13541372
# Cell.__hash__ light-weight to minimise performance degradation.
1373+
# Also see Cell.__init__ for Cell._hash assignment.
13551374
# Reference:
13561375
# - https://bugs.python.org/issue43475
13571376
# - https://github.com/numpy/numpy/issues/18833
13581377
# - https://github.com/numpy/numpy/pull/18908
13591378
# - https://github.com/numpy/numpy/issues/21210
1360-
1361-
try:
1362-
point = "nan" if np.isnan(self.point) else self.point
1363-
except TypeError:
1364-
point = self.point
1365-
return hash((point,))
1379+
return self._hash
13661380

13671381
def __eq__(self, other):
13681382
"""

lib/iris/tests/unit/coords/test_Cell.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,24 @@ def test_nan_other(self):
155155
self.assertEqual(cell1, cell2)
156156

157157

158+
class Test___hash__(tests.IrisTest):
159+
def test_nan__hash(self):
160+
cell = Cell(np.nan, None)
161+
self.assertEqual(cell._hash, 0)
162+
163+
def test_nan___hash__(self):
164+
cell = Cell(np.nan, None)
165+
self.assertEqual(cell.__hash__(), 0)
166+
167+
def test_non_nan__hash(self):
168+
cell = Cell(1, None)
169+
self.assertNotEqual(cell._hash, 0)
170+
171+
def test_non_nan___hash__(self):
172+
cell = Cell("two", ("one", "three"))
173+
self.assertNotEqual(cell.__hash__(), 0)
174+
175+
158176
class Test_contains_point(tests.IrisTest):
159177
def test_datetimelike_bounded_cell(self):
160178
point = object()

0 commit comments

Comments
 (0)