From 5c3f1c551f2e3f3a39dd6a6c9ab63f891a3c7615 Mon Sep 17 00:00:00 2001 From: chinskiy Date: Mon, 14 Mar 2016 01:20:57 +0200 Subject: [PATCH 1/2] BUG: ensure Series.name is hashable #12610 --- doc/source/whatsnew/v0.18.1.txt | 5 +++++ pandas/core/generic.py | 2 +- pandas/core/series.py | 12 +++++++++++- pandas/tests/series/test_alter_axes.py | 6 ++++-- pandas/tests/series/test_constructors.py | 11 +++++++++++ pandas/tests/series/test_io.py | 5 +++-- pandas/tests/series/test_repr.py | 2 +- 7 files changed, 36 insertions(+), 7 deletions(-) diff --git a/doc/source/whatsnew/v0.18.1.txt b/doc/source/whatsnew/v0.18.1.txt index e664020946baf..749b6ef39aa18 100644 --- a/doc/source/whatsnew/v0.18.1.txt +++ b/doc/source/whatsnew/v0.18.1.txt @@ -165,3 +165,8 @@ Bug Fixes - Bug in ``pivot_table`` when ``margins=True`` and ``dropna=True`` where nulls still contributed to margin count (:issue:`12577`) + + + + +- Bug in ``Series.name`` when ``name`` attribute can be hashable type (:issue:`12610`) \ No newline at end of file diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 24b883b90cf5d..cbcd949dd5c36 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -81,7 +81,7 @@ class NDFrame(PandasObject): copy : boolean, default False """ _internal_names = ['_data', '_cacher', '_item_cache', '_cache', 'is_copy', - '_subtyp', '_index', '_default_kind', + '_subtyp', '_name', '_index', '_default_kind', '_default_fill_value', '_metadata', '__array_struct__', '__array_interface__'] _internal_names_set = set(_internal_names) diff --git a/pandas/core/series.py b/pandas/core/series.py index b25cd63acaff6..cc58b32de999a 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -232,7 +232,7 @@ def __init__(self, data=None, index=None, dtype=None, name=None, generic.NDFrame.__init__(self, data, fastpath=True) - object.__setattr__(self, 'name', name) + self.name = name self._set_axis(0, index, fastpath=True) @classmethod @@ -301,6 +301,16 @@ def _update_inplace(self, result, **kwargs): # we want to call the generic version and not the IndexOpsMixin return generic.NDFrame._update_inplace(self, result, **kwargs) + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + if value is not None and not com.is_hashable(value): + raise TypeError('Series.name must be a hashable type') + object.__setattr__(self, '_name', value) + # ndarray compatibility @property def dtype(self): diff --git a/pandas/tests/series/test_alter_axes.py b/pandas/tests/series/test_alter_axes.py index 4f9a55908fe96..010a4a3d3585d 100644 --- a/pandas/tests/series/test_alter_axes.py +++ b/pandas/tests/series/test_alter_axes.py @@ -1,6 +1,8 @@ # coding=utf-8 # pylint: disable-msg=E1101,W0612 +from datetime import datetime + import numpy as np import pandas as pd @@ -64,7 +66,7 @@ def test_rename_by_series(self): def test_rename_set_name(self): s = Series(range(4), index=list('abcd')) - for name in ['foo', ['foo'], ('foo',)]: + for name in ['foo', 123, 123., datetime(2001, 11, 11), ('foo',)]: result = s.rename(name) self.assertEqual(result.name, name) self.assert_numpy_array_equal(result.index.values, s.index.values) @@ -72,7 +74,7 @@ def test_rename_set_name(self): def test_rename_set_name_inplace(self): s = Series(range(3), index=list('abc')) - for name in ['foo', ['foo'], ('foo',)]: + for name in ['foo', 123, 123., datetime(2001, 11, 11), ('foo',)]: s.rename(name, inplace=True) self.assertEqual(s.name, name) self.assert_numpy_array_equal(s.index.values, diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 356267630b274..083750f2a2543 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -725,3 +725,14 @@ def f(): self.assertEqual(s.dtype, 'timedelta64[ns]') s = Series([pd.NaT, np.nan, '1 Day']) self.assertEqual(s.dtype, 'timedelta64[ns]') + + def test_constructor_name_hashable(self): + for n in [777, 777., 'name', datetime(2001, 11, 11), (1, 2)]: + for data in [[1, 2, 3], np.ones(3), {'a': 0, 'b': 1}]: + s = Series(data, name=n) + self.assertEqual(s.name, n) + + def test_constructor_name_unhashable(self): + for n in [['name_list'], np.ones(2), {1: 2}]: + for data in [['name_list'], np.ones(2), {1: 2}]: + self.assertRaises(TypeError, Series, data, name=n) diff --git a/pandas/tests/series/test_io.py b/pandas/tests/series/test_io.py index beb023215e6ce..2cdd6b22afc74 100644 --- a/pandas/tests/series/test_io.py +++ b/pandas/tests/series/test_io.py @@ -146,8 +146,9 @@ def test_timeseries_periodindex(self): self.assertEqual(new_ts.index.freq, 'M') def test_pickle_preserve_name(self): - unpickled = self._pickle_roundtrip_name(self.ts) - self.assertEqual(unpickled.name, self.ts.name) + for n in [777, 777., 'name', datetime(2001, 11, 11), (1, 2)]: + unpickled = self._pickle_roundtrip_name(tm.makeTimeSeries(name=n)) + self.assertEqual(unpickled.name, n) def _pickle_roundtrip_name(self, obj): diff --git a/pandas/tests/series/test_repr.py b/pandas/tests/series/test_repr.py index ec2efdcf40705..68ad29f1418c3 100644 --- a/pandas/tests/series/test_repr.py +++ b/pandas/tests/series/test_repr.py @@ -97,7 +97,7 @@ def test_repr(self): rep_str = repr(ser) self.assertIn("Name: 0", rep_str) - ser = Series(["a\n\r\tb"], name=["a\n\r\td"], index=["a\n\r\tf"]) + ser = Series(["a\n\r\tb"], name="a\n\r\td", index=["a\n\r\tf"]) self.assertFalse("\t" in repr(ser)) self.assertFalse("\r" in repr(ser)) self.assertFalse("a\n" in repr(ser)) From fc077b7b334918034468b36d5ac3c370ffce5d91 Mon Sep 17 00:00:00 2001 From: chinskiy Date: Wed, 23 Mar 2016 16:55:46 +0200 Subject: [PATCH 2/2] BUG: ensure Series.name is hashable #12610 add more tests --- doc/source/whatsnew/v0.18.1.txt | 6 +----- pandas/tests/series/test_alter_axes.py | 6 ++++++ pandas/tests/series/test_constructors.py | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/doc/source/whatsnew/v0.18.1.txt b/doc/source/whatsnew/v0.18.1.txt index 749b6ef39aa18..7701bc66b8b92 100644 --- a/doc/source/whatsnew/v0.18.1.txt +++ b/doc/source/whatsnew/v0.18.1.txt @@ -165,8 +165,4 @@ Bug Fixes - Bug in ``pivot_table`` when ``margins=True`` and ``dropna=True`` where nulls still contributed to margin count (:issue:`12577`) - - - - -- Bug in ``Series.name`` when ``name`` attribute can be hashable type (:issue:`12610`) \ No newline at end of file +- Bug in ``Series.name`` when ``name`` attribute can be a hashable type (:issue:`12610`) \ No newline at end of file diff --git a/pandas/tests/series/test_alter_axes.py b/pandas/tests/series/test_alter_axes.py index 010a4a3d3585d..ee054437e60a8 100644 --- a/pandas/tests/series/test_alter_axes.py +++ b/pandas/tests/series/test_alter_axes.py @@ -80,6 +80,12 @@ def test_rename_set_name_inplace(self): self.assert_numpy_array_equal(s.index.values, np.array(['a', 'b', 'c'])) + def test_set_name_attribute(self): + s = Series([1, 2, 3]) + for name in [7, 7., 'name', datetime(2001, 1, 1), (1,), u"\u05D0"]: + s.name = name + self.assertEqual(s.name, name) + def test_set_name(self): s = Series([1, 2, 3]) s2 = s._set_name('foo') diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 083750f2a2543..89793018b5193 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -727,7 +727,7 @@ def f(): self.assertEqual(s.dtype, 'timedelta64[ns]') def test_constructor_name_hashable(self): - for n in [777, 777., 'name', datetime(2001, 11, 11), (1, 2)]: + for n in [777, 777., 'name', datetime(2001, 11, 11), (1, ), u"\u05D0"]: for data in [[1, 2, 3], np.ones(3), {'a': 0, 'b': 1}]: s = Series(data, name=n) self.assertEqual(s.name, n)