Skip to content

Commit 30c43b6

Browse files
committed
BUG: raise on non-hashable in __contains__
1 parent 447a3b0 commit 30c43b6

File tree

7 files changed

+24
-8
lines changed

7 files changed

+24
-8
lines changed

pandas/_libs/index.pyx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,10 @@ cdef class IndexEngine:
7272
self.over_size_threshold = n >= _SIZE_CUTOFF
7373
self.clear_mapping()
7474

75-
def __contains__(self, object val):
75+
def __contains__(self, val: object) -> bool:
76+
# We assume before we get here:
77+
# - val is hashable
7678
self._ensure_mapping_populated()
77-
hash(val)
7879
return val in self.mapping
7980

8081
cpdef get_value(self, ndarray arr, object key, object tz=None):
@@ -85,7 +86,6 @@ cdef class IndexEngine:
8586
"""
8687
cdef:
8788
object loc
88-
void* data_ptr
8989

9090
loc = self.get_loc(key)
9191
if isinstance(loc, slice) or util.is_array(loc):
@@ -101,7 +101,6 @@ cdef class IndexEngine:
101101
"""
102102
cdef:
103103
object loc
104-
void* data_ptr
105104

106105
loc = self.get_loc(key)
107106
value = convert_scalar(arr, value)
@@ -409,7 +408,9 @@ cdef class DatetimeEngine(Int64Engine):
409408
cdef _get_box_dtype(self):
410409
return 'M8[ns]'
411410

412-
def __contains__(self, object val):
411+
def __contains__(self, val: object) -> bool:
412+
# We assume before we get here:
413+
# - val is hashable
413414
cdef:
414415
int64_t loc
415416

@@ -717,7 +718,9 @@ cdef class BaseMultiIndexCodesEngine:
717718

718719
return indexer
719720

720-
def __contains__(self, object val):
721+
def __contains__(self, val: object) -> bool:
722+
# We assume before we get here:
723+
# - val is hashable
721724
# Default __contains__ looks in the underlying mapping, which in this
722725
# case only contains integer representations.
723726
try:

pandas/core/indexes/category.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ def __contains__(self, key) -> bool:
390390
if is_scalar(key) and isna(key):
391391
return self.hasnans
392392

393+
hash(key)
393394
return contains(self, key, container=self._engine)
394395

395396
def __array__(self, dtype=None) -> np.ndarray:

pandas/core/indexes/datetimelike.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ def equals(self, other) -> bool:
154154

155155
@Appender(_index_shared_docs["contains"] % _index_doc_kwargs)
156156
def __contains__(self, key):
157+
hash(key)
157158
try:
158159
res = self.get_loc(key)
159160
return (

pandas/core/indexes/interval.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ def __contains__(self, key) -> bool:
385385
-------
386386
bool
387387
"""
388+
hash(key)
388389
if not isinstance(key, Interval):
389390
return False
390391

pandas/core/indexes/numeric.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,7 @@ def equals(self, other) -> bool:
474474
return False
475475

476476
def __contains__(self, other) -> bool:
477+
hash(other)
477478
if super().__contains__(other):
478479
return True
479480

pandas/core/indexes/period.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,11 +377,11 @@ def __contains__(self, key) -> bool:
377377
else:
378378
return key.ordinal in self._engine
379379
else:
380+
hash(key)
380381
try:
381382
self.get_loc(key)
382383
return True
383-
except (TypeError, KeyError):
384-
# TypeError can be reached if we pass a tuple that is not hashable
384+
except KeyError:
385385
return False
386386

387387
@cache_readonly

pandas/tests/indexes/common.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
PeriodIndex,
2020
RangeIndex,
2121
Series,
22+
Float64Index,
2223
TimedeltaIndex,
2324
UInt64Index,
2425
isna,
@@ -883,3 +884,11 @@ def test_getitem_2d_deprecated(self):
883884
res = idx[:, None]
884885

885886
assert isinstance(res, np.ndarray), type(res)
887+
888+
def test_contains_requires_hashable(self):
889+
idx = self.create_index()
890+
with pytest.raises(TypeError, match="unhashable type"):
891+
[] in idx
892+
893+
with pytest.raises(TypeError):
894+
{} in idx._engine

0 commit comments

Comments
 (0)