|
1 | 1 | from datetime import datetime |
2 | 2 | import operator |
3 | 3 | from textwrap import dedent |
4 | | -from typing import FrozenSet, Union |
| 4 | +from typing import FrozenSet, Hashable, Optional, Union |
5 | 5 | import warnings |
6 | 6 |
|
7 | 7 | import numpy as np |
@@ -239,7 +239,7 @@ def _outer_indexer(self, left, right): |
239 | 239 | _typ = "index" |
240 | 240 | _data: Union[ExtensionArray, np.ndarray] |
241 | 241 | _id = None |
242 | | - name = None |
| 242 | + _name: Optional[Hashable] = None |
243 | 243 | _comparables = ["name"] |
244 | 244 | _attributes = ["name"] |
245 | 245 | _is_numeric_dtype = False |
@@ -274,8 +274,7 @@ def __new__( |
274 | 274 | from .interval import IntervalIndex |
275 | 275 | from .category import CategoricalIndex |
276 | 276 |
|
277 | | - if name is None and hasattr(data, "name"): |
278 | | - name = data.name |
| 277 | + name = maybe_extract_name(name, data, cls) |
279 | 278 |
|
280 | 279 | if isinstance(data, ABCPandasArray): |
281 | 280 | # ensure users don't accidentally put a PandasArray in an index. |
@@ -520,7 +519,7 @@ def _simple_new(cls, values, name=None, dtype=None): |
520 | 519 | # data buffers and strides. We don't re-use `_ndarray_values`, since |
521 | 520 | # we actually set this value too. |
522 | 521 | result._index_data = values |
523 | | - result.name = name |
| 522 | + result._name = name |
524 | 523 |
|
525 | 524 | return result._reset_identity() |
526 | 525 |
|
@@ -1209,6 +1208,15 @@ def to_frame(self, index=True, name=None): |
1209 | 1208 | # -------------------------------------------------------------------- |
1210 | 1209 | # Name-Centric Methods |
1211 | 1210 |
|
| 1211 | + @property |
| 1212 | + def name(self): |
| 1213 | + return self._name |
| 1214 | + |
| 1215 | + @name.setter |
| 1216 | + def name(self, value): |
| 1217 | + maybe_extract_name(value, None, type(self)) |
| 1218 | + self._name = value |
| 1219 | + |
1212 | 1220 | def _validate_names(self, name=None, names=None, deep=False): |
1213 | 1221 | """ |
1214 | 1222 | Handles the quirks of having a singular 'name' parameter for general |
@@ -1258,7 +1266,7 @@ def _set_names(self, values, level=None): |
1258 | 1266 | for name in values: |
1259 | 1267 | if not is_hashable(name): |
1260 | 1268 | raise TypeError(f"{type(self).__name__}.name must be a hashable type") |
1261 | | - self.name = values[0] |
| 1269 | + self._name = values[0] |
1262 | 1270 |
|
1263 | 1271 | names = property(fset=_set_names, fget=_get_names) |
1264 | 1272 |
|
@@ -1546,7 +1554,7 @@ def droplevel(self, level=0): |
1546 | 1554 | if mask.any(): |
1547 | 1555 | result = result.putmask(mask, np.nan) |
1548 | 1556 |
|
1549 | | - result.name = new_names[0] |
| 1557 | + result._name = new_names[0] |
1550 | 1558 | return result |
1551 | 1559 | else: |
1552 | 1560 | from .multi import MultiIndex |
@@ -1777,7 +1785,7 @@ def __setstate__(self, state): |
1777 | 1785 | nd_state, own_state = state |
1778 | 1786 | data = np.empty(nd_state[1], dtype=nd_state[2]) |
1779 | 1787 | np.ndarray.__setstate__(data, nd_state) |
1780 | | - self.name = own_state[0] |
| 1788 | + self._name = own_state[0] |
1781 | 1789 |
|
1782 | 1790 | else: # pragma: no cover |
1783 | 1791 | data = np.empty(state) |
@@ -5462,3 +5470,19 @@ def default_index(n): |
5462 | 5470 | from pandas.core.indexes.range import RangeIndex |
5463 | 5471 |
|
5464 | 5472 | return RangeIndex(0, n, name=None) |
| 5473 | + |
| 5474 | + |
| 5475 | +def maybe_extract_name(name, obj, cls) -> Optional[Hashable]: |
| 5476 | + """ |
| 5477 | + If no name is passed, then extract it from data, validating hashability. |
| 5478 | + """ |
| 5479 | + if name is None and isinstance(obj, (Index, ABCSeries)): |
| 5480 | + # Note we don't just check for "name" attribute since that would |
| 5481 | + # pick up e.g. dtype.name |
| 5482 | + name = obj.name |
| 5483 | + |
| 5484 | + # GH#29069 |
| 5485 | + if not is_hashable(name): |
| 5486 | + raise TypeError(f"{cls.__name__}.name must be a hashable type") |
| 5487 | + |
| 5488 | + return name |
0 commit comments