Skip to content

Commit cd96ad8

Browse files
authored
TYP Series and DataFrame currently type-check as hashable (#41283)
1 parent a5de849 commit cd96ad8

File tree

9 files changed

+29
-25
lines changed

9 files changed

+29
-25
lines changed

doc/source/whatsnew/v1.3.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,7 @@ Other API changes
707707
- Added new ``engine`` and ``**engine_kwargs`` parameters to :meth:`DataFrame.to_sql` to support other future "SQL engines". Currently we still only use ``SQLAlchemy`` under the hood, but more engines are planned to be supported such as `turbodbc <https://turbodbc.readthedocs.io/en/latest/>`_ (:issue:`36893`)
708708
- Removed redundant ``freq`` from :class:`PeriodIndex` string representation (:issue:`41653`)
709709
- :meth:`ExtensionDtype.construct_array_type` is now a required method instead of an optional one for :class:`ExtensionDtype` subclasses (:issue:`24860`)
710+
- Calling ``hash`` on non-hashable pandas objects will now raise ``TypeError`` with the built-in error message (e.g. ``unhashable type: 'Series'``). Previously it would raise a custom message such as ``'Series' objects are mutable, thus they cannot be hashed``. Furthermore, ``isinstance(<Series>, abc.collections.Hashable)`` will now return ``False`` (:issue:`40013`)
710711
- :meth:`.Styler.from_custom_template` now has two new arguments for template names, and removed the old ``name``, due to template inheritance having been introducing for better parsing (:issue:`42053`). Subclassing modifications to Styler attributes are also needed.
711712

712713
.. _whatsnew_130.api_breaking.build:

pandas/core/arrays/base.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1296,8 +1296,10 @@ def _reduce(self, name: str, *, skipna: bool = True, **kwargs):
12961296
"""
12971297
raise TypeError(f"cannot perform {name} with type {self.dtype}")
12981298

1299-
def __hash__(self) -> int:
1300-
raise TypeError(f"unhashable type: {repr(type(self).__name__)}")
1299+
# https://github.com/python/typeshed/issues/2148#issuecomment-520783318
1300+
# Incompatible types in assignment (expression has type "None", base class
1301+
# "object" defined the type as "Callable[[object], int]")
1302+
__hash__: None # type: ignore[assignment]
13011303

13021304
# ------------------------------------------------------------------------
13031305
# Non-Optimized Default Methods

pandas/core/frame.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -6180,7 +6180,10 @@ def f(vals) -> tuple[np.ndarray, int]:
61806180
return labels.astype("i8", copy=False), len(shape)
61816181

61826182
if subset is None:
6183-
subset = self.columns
6183+
# Incompatible types in assignment
6184+
# (expression has type "Index", variable has type "Sequence[Any]")
6185+
# (pending on https://github.com/pandas-dev/pandas/issues/28770)
6186+
subset = self.columns # type: ignore[assignment]
61846187
elif (
61856188
not np.iterable(subset)
61866189
or isinstance(subset, str)

pandas/core/generic.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -1874,11 +1874,10 @@ def _drop_labels_or_levels(self, keys, axis: int = 0):
18741874
# ----------------------------------------------------------------------
18751875
# Iteration
18761876

1877-
def __hash__(self) -> int:
1878-
raise TypeError(
1879-
f"{repr(type(self).__name__)} objects are mutable, "
1880-
f"thus they cannot be hashed"
1881-
)
1877+
# https://github.com/python/typeshed/issues/2148#issuecomment-520783318
1878+
# Incompatible types in assignment (expression has type "None", base class
1879+
# "object" defined the type as "Callable[[object], int]")
1880+
__hash__: None # type: ignore[assignment]
18821881

18831882
def __iter__(self):
18841883
"""

pandas/core/indexes/base.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -4604,9 +4604,10 @@ def __contains__(self, key: Any) -> bool:
46044604
except (OverflowError, TypeError, ValueError):
46054605
return False
46064606

4607-
@final
4608-
def __hash__(self):
4609-
raise TypeError(f"unhashable type: {repr(type(self).__name__)}")
4607+
# https://github.com/python/typeshed/issues/2148#issuecomment-520783318
4608+
# Incompatible types in assignment (expression has type "None", base class
4609+
# "object" defined the type as "Callable[[object], int]")
4610+
__hash__: None # type: ignore[assignment]
46104611

46114612
@final
46124613
def __setitem__(self, key, value):

pandas/core/reshape/pivot.py

+10-11
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ def pivot(
482482
if columns is None:
483483
raise TypeError("pivot() missing 1 required argument: 'columns'")
484484

485-
columns = com.convert_to_list_like(columns)
485+
columns_listlike = com.convert_to_list_like(columns)
486486

487487
if values is None:
488488
if index is not None:
@@ -494,28 +494,27 @@ def pivot(
494494
# error: Unsupported operand types for + ("List[Any]" and "ExtensionArray")
495495
# error: Unsupported left operand type for + ("ExtensionArray")
496496
indexed = data.set_index(
497-
cols + columns, append=append # type: ignore[operator]
497+
cols + columns_listlike, append=append # type: ignore[operator]
498498
)
499499
else:
500500
if index is None:
501-
index = [Series(data.index, name=data.index.name)]
501+
index_list = [Series(data.index, name=data.index.name)]
502502
else:
503-
index = com.convert_to_list_like(index)
504-
index = [data[idx] for idx in index]
503+
index_list = [data[idx] for idx in com.convert_to_list_like(index)]
505504

506-
data_columns = [data[col] for col in columns]
507-
index.extend(data_columns)
508-
index = MultiIndex.from_arrays(index)
505+
data_columns = [data[col] for col in columns_listlike]
506+
index_list.extend(data_columns)
507+
multiindex = MultiIndex.from_arrays(index_list)
509508

510509
if is_list_like(values) and not isinstance(values, tuple):
511510
# Exclude tuple because it is seen as a single column name
512511
values = cast(Sequence[Hashable], values)
513512
indexed = data._constructor(
514-
data[values]._values, index=index, columns=values
513+
data[values]._values, index=multiindex, columns=values
515514
)
516515
else:
517-
indexed = data._constructor_sliced(data[values]._values, index=index)
518-
return indexed.unstack(columns)
516+
indexed = data._constructor_sliced(data[values]._values, index=multiindex)
517+
return indexed.unstack(columns_listlike)
519518

520519

521520
def crosstab(

pandas/core/series.py

-1
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,6 @@ class Series(base.IndexOpsMixin, generic.NDFrame):
305305
hasnans = property( # type: ignore[assignment]
306306
base.IndexOpsMixin.hasnans.func, doc=base.IndexOpsMixin.hasnans.__doc__
307307
)
308-
__hash__ = generic.NDFrame.__hash__
309308
_mgr: SingleManager
310309
div: Callable[[Series, Any], Series]
311310
rdiv: Callable[[Series, Any], Series]

pandas/tests/frame/test_api.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def test_not_hashable(self):
9191
empty_frame = DataFrame()
9292

9393
df = DataFrame([1])
94-
msg = "'DataFrame' objects are mutable, thus they cannot be hashed"
94+
msg = "unhashable type: 'DataFrame'"
9595
with pytest.raises(TypeError, match=msg):
9696
hash(df)
9797
with pytest.raises(TypeError, match=msg):

pandas/tests/series/test_api.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def test_index_tab_completion(self, index):
101101
def test_not_hashable(self):
102102
s_empty = Series(dtype=object)
103103
s = Series([1])
104-
msg = "'Series' objects are mutable, thus they cannot be hashed"
104+
msg = "unhashable type: 'Series'"
105105
with pytest.raises(TypeError, match=msg):
106106
hash(s_empty)
107107
with pytest.raises(TypeError, match=msg):

0 commit comments

Comments
 (0)