diff --git a/doc/source/whatsnew/v0.16.0.txt b/doc/source/whatsnew/v0.16.0.txt index bc51519898359..9467764ec2a74 100644 --- a/doc/source/whatsnew/v0.16.0.txt +++ b/doc/source/whatsnew/v0.16.0.txt @@ -245,6 +245,7 @@ Backwards incompatible API changes - Bar and horizontal bar plots no longer add a dashed line along the info axis. The prior style can be achieved with matplotlib's ``axhline`` or ``axvline`` methods (:issue:`9088`). +- ``Series`` accessors ``.dt``, ``.cat`` and ``.str`` now raise ``AttributeError`` instead of ``TypeError`` if the series does not contain the appropriate type of data (:issue:`9617`). This follows Python's built-in exception hierarchy more closely and ensures that tests like ``hasattr(s, 'cat')`` are consistent on both Python 2 and 3. - ``Series`` now supports bitwise operation for integral types (:issue:`9016`) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 67a9ab67c0a98..c29e97b423fe1 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -78,8 +78,9 @@ class NDFrame(PandasObject): copy : boolean, default False """ _internal_names = ['_data', '_cacher', '_item_cache', '_cache', - 'is_copy', 'str', '_subtyp', '_index', '_default_kind', - '_default_fill_value','__array_struct__','__array_interface__'] + 'is_copy', 'dt', 'cat', 'str', '_subtyp', '_index', + '_default_kind', '_default_fill_value', + '__array_struct__','__array_interface__'] _internal_names_set = set(_internal_names) _metadata = [] is_copy = None diff --git a/pandas/core/series.py b/pandas/core/series.py index 4640776f3b88e..a83a6291e6c81 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -2521,8 +2521,9 @@ def _make_str_accessor(self): # this really should exclude all series with any non-string values, # but that isn't practical for performance reasons until we have a # str dtype (GH 9343) - raise TypeError("Can only use .str accessor with string values, " - "which use np.object_ dtype in pandas") + raise AttributeError("Can only use .str accessor with string " + "values, which use np.object_ dtype in " + "pandas") return StringMethods(self) str = base.AccessorProperty(StringMethods, _make_str_accessor) @@ -2533,8 +2534,9 @@ def _make_str_accessor(self): def _make_dt_accessor(self): try: return maybe_to_datetimelike(self) - except (Exception): - raise TypeError("Can only use .dt accessor with datetimelike values") + except Exception: + raise AttributeError("Can only use .dt accessor with datetimelike " + "values") dt = base.AccessorProperty(CombinedDatetimelikeProperties, _make_dt_accessor) @@ -2543,7 +2545,8 @@ def _make_dt_accessor(self): def _make_cat_accessor(self): if not com.is_categorical_dtype(self.dtype): - raise TypeError("Can only use .cat accessor with a 'category' dtype") + raise AttributeError("Can only use .cat accessor with a " + "'category' dtype") return CategoricalAccessor(self.values, self.index) cat = base.AccessorProperty(CategoricalAccessor, _make_cat_accessor) diff --git a/pandas/tests/test_categorical.py b/pandas/tests/test_categorical.py index 63722264c25dc..a77f3984105cc 100644 --- a/pandas/tests/test_categorical.py +++ b/pandas/tests/test_categorical.py @@ -2631,8 +2631,11 @@ def test_cat_accessor_api(self): self.assertIs(Series.cat, CategoricalAccessor) s = Series(list('aabbcde')).astype('category') self.assertIsInstance(s.cat, CategoricalAccessor) - with tm.assertRaisesRegexp(TypeError, "only use .cat accessor"): - Series([1]).cat + + invalid = Series([1]) + with tm.assertRaisesRegexp(AttributeError, "only use .cat accessor"): + invalid.cat + self.assertFalse(hasattr(invalid, 'cat')) def test_pickle_v0_14_1(self): cat = pd.Categorical(values=['a', 'b', 'c'], diff --git a/pandas/tests/test_series.py b/pandas/tests/test_series.py index e5d983472256f..7e0dbaa735456 100644 --- a/pandas/tests/test_series.py +++ b/pandas/tests/test_series.py @@ -104,12 +104,6 @@ def compare(s, name): else: tm.assert_series_equal(a,b) - # invalids - for s in [Series(np.arange(5)), - Series(list('abcde')), - Series(np.random.randn(5))]: - self.assertRaises(TypeError, lambda : s.dt) - # datetimeindex for s in [Series(date_range('20130101',periods=5)), Series(date_range('20130101',periods=5,freq='s')), @@ -240,8 +234,13 @@ def test_dt_accessor_api(self): s = Series(date_range('2000-01-01', periods=3)) self.assertIsInstance(s.dt, DatetimeProperties) - with tm.assertRaisesRegexp(TypeError, "only use .dt accessor"): - Series([1]).dt + for s in [Series(np.arange(5)), + Series(list('abcde')), + Series(np.random.randn(5))]: + with tm.assertRaisesRegexp(AttributeError, + "only use .dt accessor"): + s.dt + self.assertFalse(hasattr(s, 'dt')) def test_binop_maybe_preserve_name(self): diff --git a/pandas/tests/test_strings.py b/pandas/tests/test_strings.py index 00ef017859e3d..727ef39aa35e7 100644 --- a/pandas/tests/test_strings.py +++ b/pandas/tests/test_strings.py @@ -37,8 +37,10 @@ def test_api(self): self.assertIsInstance(Series(['']).str, strings.StringMethods) # GH 9184 - with tm.assertRaisesRegexp(TypeError, "only use .str accessor"): - Series([1]).str + invalid = Series([1]) + with tm.assertRaisesRegexp(AttributeError, "only use .str accessor"): + invalid.str + self.assertFalse(hasattr(invalid, 'str')) def test_iter(self): # GH3638