Skip to content

Commit

Permalink
BUG: raise on non-hashable Index name, closes pandas-dev#29069
Browse files Browse the repository at this point in the history
  • Loading branch information
jbrockmendel committed Dec 18, 2019
1 parent 18bd98f commit 75ef96f
Show file tree
Hide file tree
Showing 10 changed files with 46 additions and 19 deletions.
17 changes: 15 additions & 2 deletions pandas/core/indexes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,7 @@ def __new__(
from .interval import IntervalIndex
from .category import CategoricalIndex

if name is None and hasattr(data, "name"):
name = data.name
name = maybe_extract_name(name, data)

if isinstance(data, ABCPandasArray):
# ensure users don't accidentally put a PandasArray in an index.
Expand Down Expand Up @@ -5464,3 +5463,17 @@ def default_index(n):
from pandas.core.indexes.range import RangeIndex

return RangeIndex(0, n, name=None)


def maybe_extract_name(name, obj):
"""
If no name is passed, then extract it from data, validating hashability.
"""
if name is None and hasattr(obj, "name"):
name = obj.name

# GH#29069
if not is_hashable(name):
raise TypeError(f"Index.name must be a hashable type")

return name
5 changes: 2 additions & 3 deletions pandas/core/indexes/category.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from pandas.core.base import _shared_docs
import pandas.core.common as com
import pandas.core.indexes.base as ibase
from pandas.core.indexes.base import Index, _index_shared_docs
from pandas.core.indexes.base import Index, _index_shared_docs, maybe_extract_name
import pandas.core.missing as missing
from pandas.core.ops import get_op_result_name

Expand Down Expand Up @@ -175,8 +175,7 @@ def __new__(

dtype = CategoricalDtype._from_values_or_dtype(data, categories, ordered, dtype)

if name is None and hasattr(data, "name"):
name = data.name
name = maybe_extract_name(name, data)

if not is_categorical_dtype(data):
# don't allow scalars
Expand Down
5 changes: 2 additions & 3 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
)
from pandas.core.base import _shared_docs
import pandas.core.common as com
from pandas.core.indexes.base import Index
from pandas.core.indexes.base import Index, maybe_extract_name
from pandas.core.indexes.datetimelike import (
DatetimeIndexOpsMixin,
DatetimelikeDelegateMixin,
Expand Down Expand Up @@ -253,8 +253,7 @@ def __new__(

# - Cases checked above all return/raise before reaching here - #

if name is None and hasattr(data, "name"):
name = data.name
name = maybe_extract_name(name, data)

dtarr = DatetimeArray._from_sequence(
data,
Expand Down
4 changes: 2 additions & 2 deletions pandas/core/indexes/interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
_index_shared_docs,
default_pprint,
ensure_index,
maybe_extract_name,
)
from pandas.core.indexes.datetimes import DatetimeIndex, date_range
from pandas.core.indexes.multi import MultiIndex
Expand Down Expand Up @@ -213,8 +214,7 @@ def __new__(
cls, data, closed=None, dtype=None, copy=False, name=None, verify_integrity=True
):

if name is None and hasattr(data, "name"):
name = data.name
name = maybe_extract_name(name, data)

with rewrite_exception("IntervalArray", cls.__name__):
array = IntervalArray(
Expand Down
10 changes: 7 additions & 3 deletions pandas/core/indexes/numeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@
from pandas._typing import Dtype
from pandas.core import algorithms
import pandas.core.common as com
from pandas.core.indexes.base import Index, InvalidIndexError, _index_shared_docs
from pandas.core.indexes.base import (
Index,
InvalidIndexError,
_index_shared_docs,
maybe_extract_name,
)
from pandas.core.ops import get_op_result_name

_num_index_shared_docs = dict()
Expand Down Expand Up @@ -68,8 +73,7 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None):
else:
subarr = data

if name is None and hasattr(data, "name"):
name = data.name
name = maybe_extract_name(name, data)
return cls._simple_new(subarr, name=name)

@classmethod
Expand Down
9 changes: 6 additions & 3 deletions pandas/core/indexes/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@
from pandas.core.base import _shared_docs
import pandas.core.common as com
import pandas.core.indexes.base as ibase
from pandas.core.indexes.base import _index_shared_docs, ensure_index
from pandas.core.indexes.base import (
_index_shared_docs,
ensure_index,
maybe_extract_name,
)
from pandas.core.indexes.datetimelike import (
DatetimeIndexOpsMixin,
DatetimelikeDelegateMixin,
Expand Down Expand Up @@ -184,8 +188,7 @@ def __new__(
argument = list(set(fields) - valid_field_set)[0]
raise TypeError(f"__new__() got an unexpected keyword argument {argument}")

if name is None and hasattr(data, "name"):
name = data.name
name = maybe_extract_name(name, data)

if data is None and ordinal is None:
# range-based.
Expand Down
4 changes: 2 additions & 2 deletions pandas/core/indexes/range.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import pandas.core.common as com
from pandas.core.construction import extract_array
import pandas.core.indexes.base as ibase
from pandas.core.indexes.base import Index, _index_shared_docs
from pandas.core.indexes.base import Index, _index_shared_docs, maybe_extract_name
from pandas.core.indexes.numeric import Int64Index
from pandas.core.ops.common import unpack_zerodim_and_defer

Expand Down Expand Up @@ -85,10 +85,10 @@ def __new__(
):

cls._validate_dtype(dtype)
name = maybe_extract_name(name, start)

# RangeIndex
if isinstance(start, RangeIndex):
name = start.name if name is None else name
start = start._range
return cls._simple_new(start, dtype=dtype, name=name)

Expand Down
3 changes: 2 additions & 1 deletion pandas/core/indexes/timedeltas.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from pandas.core.arrays.timedeltas import TimedeltaArray, _is_convertible_to_td
from pandas.core.base import _shared_docs
import pandas.core.common as com
from pandas.core.indexes.base import Index, _index_shared_docs
from pandas.core.indexes.base import Index, _index_shared_docs, maybe_extract_name
from pandas.core.indexes.datetimelike import (
DatetimeIndexOpsMixin,
DatetimelikeDelegateMixin,
Expand Down Expand Up @@ -163,6 +163,7 @@ def __new__(
copy=False,
name=None,
):
name = maybe_extract_name(name, data)

if is_scalar(data):
raise TypeError(
Expand Down
7 changes: 7 additions & 0 deletions pandas/tests/indexes/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ def test_shift(self):
with pytest.raises(NotImplementedError, match=msg):
idx.shift(1, 2)

def test_constructor_name_unhashable(self):
# GH#29069 check that name is hashable
# See also same-named test in tests.series.test_constructors
idx = self.create_index()
with pytest.raises(TypeError, match="Index.name must be a hashable type"):
type(idx)(idx, name=[])

def test_create_index_existing_name(self):

# GH11193, when an existing index is passed, and a new name is not
Expand Down
1 change: 1 addition & 0 deletions pandas/tests/indexes/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def test_constructor_copy(self, index):
arr[0] = "SOMEBIGLONGSTRING"
assert new_index[0] != "SOMEBIGLONGSTRING"

# FIXME: dont leave commented-out
# what to do here?
# arr = np.array(5.)
# pytest.raises(Exception, arr.view, Index)
Expand Down

0 comments on commit 75ef96f

Please sign in to comment.