|
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