Skip to content

Commit 2560173

Browse files
authored
REF: Simplify Index.copy (#35592)
1 parent 5dee73b commit 2560173

File tree

7 files changed

+84
-20
lines changed

7 files changed

+84
-20
lines changed

pandas/core/dtypes/common.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from pandas._libs import Interval, Period, algos
1111
from pandas._libs.tslibs import conversion
12-
from pandas._typing import ArrayLike, DtypeObj
12+
from pandas._typing import ArrayLike, DtypeObj, Optional
1313

1414
from pandas.core.dtypes.base import registry
1515
from pandas.core.dtypes.dtypes import (
@@ -1732,6 +1732,32 @@ def _validate_date_like_dtype(dtype) -> None:
17321732
)
17331733

17341734

1735+
def validate_all_hashable(*args, error_name: Optional[str] = None) -> None:
1736+
"""
1737+
Return None if all args are hashable, else raise a TypeError.
1738+
1739+
Parameters
1740+
----------
1741+
*args
1742+
Arguments to validate.
1743+
error_name : str, optional
1744+
The name to use if error
1745+
1746+
Raises
1747+
------
1748+
TypeError : If an argument is not hashable
1749+
1750+
Returns
1751+
-------
1752+
None
1753+
"""
1754+
if not all(is_hashable(arg) for arg in args):
1755+
if error_name:
1756+
raise TypeError(f"{error_name} must be a hashable type")
1757+
else:
1758+
raise TypeError("All elements must be hashable")
1759+
1760+
17351761
def pandas_dtype(dtype) -> DtypeObj:
17361762
"""
17371763
Convert input into a pandas only dtype object or a numpy dtype object.

pandas/core/indexes/base.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
is_timedelta64_dtype,
5959
is_unsigned_integer_dtype,
6060
pandas_dtype,
61+
validate_all_hashable,
6162
)
6263
from pandas.core.dtypes.concat import concat_compat
6364
from pandas.core.dtypes.generic import (
@@ -812,13 +813,11 @@ def copy(self, name=None, deep=False, dtype=None, names=None):
812813
In most cases, there should be no functional difference from using
813814
``deep``, but if ``deep`` is passed it will attempt to deepcopy.
814815
"""
816+
name = self._validate_names(name=name, names=names, deep=deep)[0]
815817
if deep:
816-
new_index = self._shallow_copy(self._data.copy())
818+
new_index = self._shallow_copy(self._data.copy(), name=name)
817819
else:
818-
new_index = self._shallow_copy()
819-
820-
names = self._validate_names(name=name, names=names, deep=deep)
821-
new_index = new_index.set_names(names)
820+
new_index = self._shallow_copy(name=name)
822821

823822
if dtype:
824823
new_index = new_index.astype(dtype)
@@ -1186,7 +1185,7 @@ def name(self, value):
11861185
maybe_extract_name(value, None, type(self))
11871186
self._name = value
11881187

1189-
def _validate_names(self, name=None, names=None, deep: bool = False):
1188+
def _validate_names(self, name=None, names=None, deep: bool = False) -> List[Label]:
11901189
"""
11911190
Handles the quirks of having a singular 'name' parameter for general
11921191
Index and plural 'names' parameter for MultiIndex.
@@ -1196,15 +1195,25 @@ def _validate_names(self, name=None, names=None, deep: bool = False):
11961195
if names is not None and name is not None:
11971196
raise TypeError("Can only provide one of `names` and `name`")
11981197
elif names is None and name is None:
1199-
return deepcopy(self.names) if deep else self.names
1198+
new_names = deepcopy(self.names) if deep else self.names
12001199
elif names is not None:
12011200
if not is_list_like(names):
12021201
raise TypeError("Must pass list-like as `names`.")
1203-
return names
1202+
new_names = names
1203+
elif not is_list_like(name):
1204+
new_names = [name]
12041205
else:
1205-
if not is_list_like(name):
1206-
return [name]
1207-
return name
1206+
new_names = name
1207+
1208+
if len(new_names) != len(self.names):
1209+
raise ValueError(
1210+
f"Length of new names must be {len(self.names)}, got {len(new_names)}"
1211+
)
1212+
1213+
# All items in 'new_names' need to be hashable
1214+
validate_all_hashable(*new_names, error_name=f"{type(self).__name__}.name")
1215+
1216+
return new_names
12081217

12091218
def _get_names(self):
12101219
return FrozenList((self.name,))
@@ -1232,9 +1241,8 @@ def _set_names(self, values, level=None):
12321241

12331242
# GH 20527
12341243
# All items in 'name' need to be hashable:
1235-
for name in values:
1236-
if not is_hashable(name):
1237-
raise TypeError(f"{type(self).__name__}.name must be a hashable type")
1244+
validate_all_hashable(*values, error_name=f"{type(self).__name__}.name")
1245+
12381246
self._name = values[0]
12391247

12401248
names = property(fset=_set_names, fget=_get_names)

pandas/core/indexes/range.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -388,9 +388,8 @@ def _shallow_copy(self, values=None, name: Label = no_default):
388388
def copy(self, name=None, deep=False, dtype=None, names=None):
389389
self._validate_dtype(dtype)
390390

391-
new_index = self._shallow_copy()
392-
names = self._validate_names(name=name, names=names, deep=deep)
393-
new_index = new_index.set_names(names)
391+
name = self._validate_names(name=name, names=names, deep=deep)[0]
392+
new_index = self._shallow_copy(name=name)
394393
return new_index
395394

396395
def _minmax(self, meth: str):

pandas/core/series.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
is_list_like,
5555
is_object_dtype,
5656
is_scalar,
57+
validate_all_hashable,
5758
)
5859
from pandas.core.dtypes.generic import ABCDataFrame
5960
from pandas.core.dtypes.inference import is_hashable
@@ -491,8 +492,7 @@ def name(self) -> Label:
491492

492493
@name.setter
493494
def name(self, value: Label) -> None:
494-
if not is_hashable(value):
495-
raise TypeError("Series.name must be a hashable type")
495+
validate_all_hashable(value, error_name=f"{type(self).__name__}.name")
496496
object.__setattr__(self, "_name", value)
497497

498498
@property

pandas/tests/dtypes/test_common.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,3 +746,13 @@ def test_astype_object_preserves_datetime_na(from_type):
746746
result = astype_nansafe(arr, dtype="object")
747747

748748
assert isna(result)[0]
749+
750+
751+
def test_validate_allhashable():
752+
assert com.validate_all_hashable(1, "a") is None
753+
754+
with pytest.raises(TypeError, match="All elements must be hashable"):
755+
com.validate_all_hashable([])
756+
757+
with pytest.raises(TypeError, match="list must be a hashable type"):
758+
com.validate_all_hashable([], error_name="list")

pandas/tests/indexes/common.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,20 @@ def test_copy_name(self, index):
270270
s3 = s1 * s2
271271
assert s3.index.name == "mario"
272272

273+
def test_name2(self, index):
274+
# gh-35592
275+
if isinstance(index, MultiIndex):
276+
return
277+
278+
assert index.copy(name="mario").name == "mario"
279+
280+
with pytest.raises(ValueError, match="Length of new names must be 1, got 2"):
281+
index.copy(name=["mario", "luigi"])
282+
283+
msg = f"{type(index).__name__}.name must be a hashable type"
284+
with pytest.raises(TypeError, match=msg):
285+
index.copy(name=[["mario"]])
286+
273287
def test_ensure_copied_data(self, index):
274288
# Check the "copy" argument of each Index.__new__ is honoured
275289
# GH12309

pandas/tests/indexes/multi/test_names.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ def test_copy_names():
7575
assert multi_idx.names == ["MyName1", "MyName2"]
7676
assert multi_idx3.names == ["NewName1", "NewName2"]
7777

78+
# gh-35592
79+
with pytest.raises(ValueError, match="Length of new names must be 2, got 1"):
80+
multi_idx.copy(names=["mario"])
81+
82+
with pytest.raises(TypeError, match="MultiIndex.name must be a hashable type"):
83+
multi_idx.copy(names=[["mario"], ["luigi"]])
84+
7885

7986
def test_names(idx, index_names):
8087

0 commit comments

Comments
 (0)