Skip to content

Commit

Permalink
API: Raise when setting via name
Browse files Browse the repository at this point in the history
  • Loading branch information
TomAugspurger committed Dec 30, 2019
1 parent fd7db98 commit 2dd305c
Show file tree
Hide file tree
Showing 11 changed files with 36 additions and 11 deletions.
14 changes: 5 additions & 9 deletions doc/source/user_guide/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -565,19 +565,15 @@ When working with an ``Index`` object directly, rather than via a ``DataFrame``,
mi2 = mi.rename("new name", level=0)
mi2
.. warning::
Prior to pandas 1.0.0, you could also set the names of a ``MultiIndex``
by updating the name of a level.
You cannot set the names of the MultiIndex via a level.

.. code-block:: none
.. ipython:: python
:okexcept:
>>> mi.levels[0].name = 'name via level'
>>> mi.names[0] # only works for older pandas
'name via level'
mi.levels[0].name = "name via level"
As of pandas 1.0, this will *silently* fail to update the names
of the MultiIndex. Use :meth:`Index.set_names` instead.
Use :meth:`Index.set_names` instead.

Sorting a ``MultiIndex``
------------------------
Expand Down
4 changes: 2 additions & 2 deletions doc/source/whatsnew/v1.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,10 @@ For backwards compatibility, you can still *access* the names via the levels.
mi.levels[0].name
However, it is no longer possible to *update* the names of the ``MultiIndex``
via the name of the level. The following will **silently** fail to update the
name of the ``MultiIndex``
via the level.

.. ipython:: python
:okexcept:
mi.levels[0].name = "new name"
mi.names
Expand Down
11 changes: 11 additions & 0 deletions pandas/core/indexes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ def _outer_indexer(self, left, right):
_data: Union[ExtensionArray, np.ndarray]
_id = None
_name: Optional[Hashable] = None
# MultiIndex.levels previously allowed setting the index name. We
# don't allow this anymore, and raise if it happens rather than
# failing silently.
_no_setting_name: bool = False
_comparables = ["name"]
_attributes = ["name"]
_is_numeric_dtype = False
Expand Down Expand Up @@ -520,6 +524,7 @@ def _simple_new(cls, values, name=None, dtype=None):
# we actually set this value too.
result._index_data = values
result._name = name
result._no_setting_name = False

return result._reset_identity()

Expand Down Expand Up @@ -1214,6 +1219,12 @@ def name(self):

@name.setter
def name(self, value):
if self._no_setting_name:
# Used in MultiIndex.levels.
raise RuntimeError(
"Cannot set name on a level of a MultiIndex. Use "
"'MultiIndex.set_names' instead."
)
maybe_extract_name(value, None, type(self))
self._name = value

Expand Down
1 change: 1 addition & 0 deletions pandas/core/indexes/category.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ def _simple_new(cls, values, name=None, dtype=None, **kwargs):
setattr(result, k, v)

result._reset_identity()
result._no_setting_name = False
return result

# --------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ def _simple_new(cls, values, name=None, freq=None, tz=None, dtype=None):
result = object.__new__(cls)
result._data = dtarr
result.name = name
result._no_setting_name = False
# For groupby perf. See note in indexes/base about _index_data
result._index_data = dtarr._data
result._reset_identity()
Expand Down
1 change: 1 addition & 0 deletions pandas/core/indexes/interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ def _simple_new(cls, array, name, closed=None):
result = IntervalMixin.__new__(cls)
result._data = array
result.name = name
result._no_setting_name = False
result._reset_identity()
return result

Expand Down
3 changes: 3 additions & 0 deletions pandas/core/indexes/multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,9 @@ def levels(self):
result = [
x._shallow_copy(name=name) for x, name in zip(self._levels, self._names)
]
for level in result:
# disallow midx.levels[0].name = "foo"
level._no_setting_name = True
return FrozenList(result)

@property
Expand Down
1 change: 1 addition & 0 deletions pandas/core/indexes/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ def _simple_new(cls, values, name=None, freq=None, **kwargs):
# For groupby perf. See note in indexes/base about _index_data
result._index_data = values._data
result.name = name
result._no_setting_name = False
result._reset_identity()
return result

Expand Down
1 change: 1 addition & 0 deletions pandas/core/indexes/range.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ def _simple_new(cls, values, name=None, dtype=None):

result._range = values
result.name = name
result._no_setting_name = False

result._reset_identity()
return result
Expand Down
1 change: 1 addition & 0 deletions pandas/core/indexes/timedeltas.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ def _simple_new(cls, values, name=None, freq=None, dtype=_TD_DTYPE):
result = object.__new__(cls)
result._data = tdarr
result._name = name
result._no_setting_name = False
# For groupby perf. See note in indexes/base about _index_data
result._index_data = tdarr._data

Expand Down
9 changes: 9 additions & 0 deletions pandas/tests/indexes/multi/test_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,12 @@ def test_get_names_from_levels():

assert idx.levels[0].name == "a"
assert idx.levels[1].name == "b"


def test_setting_names_from_levels_raises():
idx = pd.MultiIndex.from_product([["a"], [1, 2]], names=["a", "b"])
with pytest.raises(RuntimeError, match="set_names"):
idx.levels[0].name = "foo"

with pytest.raises(RuntimeError, match="set_names"):
idx.levels[1].name = "foo"

0 comments on commit 2dd305c

Please sign in to comment.