Skip to content

Commit 7bef6d8

Browse files
jbrockmendeljreback
authored andcommitted
Implement _make_accessor classmethod for PandasDelegate (#17166)
1 parent 5d8319e commit 7bef6d8

File tree

5 files changed

+42
-37
lines changed

5 files changed

+42
-37
lines changed

pandas/core/base.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ def __setattr__(self, key, value):
165165
class PandasDelegate(PandasObject):
166166
""" an abstract base class for delegating methods/properties """
167167

168+
@classmethod
169+
def _make_accessor(cls, data):
170+
raise AbstractMethodError("_make_accessor should be implemented"
171+
"by subclass and return an instance"
172+
"of `cls`.")
173+
168174
def _delegate_property_get(self, name, *args, **kwargs):
169175
raise TypeError("You cannot access the "
170176
"property {name}".format(name=name))
@@ -231,9 +237,10 @@ class AccessorProperty(object):
231237
"""Descriptor for implementing accessor properties like Series.str
232238
"""
233239

234-
def __init__(self, accessor_cls, construct_accessor):
240+
def __init__(self, accessor_cls, construct_accessor=None):
235241
self.accessor_cls = accessor_cls
236-
self.construct_accessor = construct_accessor
242+
self.construct_accessor = (construct_accessor or
243+
accessor_cls._make_accessor)
237244
self.__doc__ = accessor_cls.__doc__
238245

239246
def __get__(self, instance, owner=None):

pandas/core/categorical.py

+7
Original file line numberDiff line numberDiff line change
@@ -2061,6 +2061,13 @@ def _delegate_method(self, name, *args, **kwargs):
20612061
if res is not None:
20622062
return Series(res, index=self.index)
20632063

2064+
@classmethod
2065+
def _make_accessor(cls, data):
2066+
if not is_categorical_dtype(data.dtype):
2067+
raise AttributeError("Can only use .cat accessor with a "
2068+
"'category' dtype")
2069+
return CategoricalAccessor(data.values, data.index)
2070+
20642071

20652072
CategoricalAccessor._add_delegate_accessors(delegate=Categorical,
20662073
accessors=["categories",

pandas/core/indexes/accessors.py

+8
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,11 @@ class CombinedDatetimelikeProperties(DatetimeProperties, TimedeltaProperties):
243243
# the Series.dt class property. For Series objects, .dt will always be one
244244
# of the more specific classes above.
245245
__doc__ = DatetimeProperties.__doc__
246+
247+
@classmethod
248+
def _make_accessor(cls, data):
249+
try:
250+
return maybe_to_datetimelike(data)
251+
except Exception:
252+
raise AttributeError("Can only use .dt accessor with "
253+
"datetimelike values")

pandas/core/series.py

+3-20
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@
5454
from pandas.core.internals import SingleBlockManager
5555
from pandas.core.categorical import Categorical, CategoricalAccessor
5656
import pandas.core.strings as strings
57-
from pandas.core.indexes.accessors import (
58-
maybe_to_datetimelike, CombinedDatetimelikeProperties)
57+
from pandas.core.indexes.accessors import CombinedDatetimelikeProperties
5958
from pandas.core.indexes.datetimes import DatetimeIndex
6059
from pandas.core.indexes.timedeltas import TimedeltaIndex
6160
from pandas.core.indexes.period import PeriodIndex
@@ -2919,27 +2918,11 @@ def to_period(self, freq=None, copy=True):
29192918

29202919
# -------------------------------------------------------------------------
29212920
# Datetimelike delegation methods
2922-
2923-
def _make_dt_accessor(self):
2924-
try:
2925-
return maybe_to_datetimelike(self)
2926-
except Exception:
2927-
raise AttributeError("Can only use .dt accessor with datetimelike "
2928-
"values")
2929-
2930-
dt = base.AccessorProperty(CombinedDatetimelikeProperties,
2931-
_make_dt_accessor)
2921+
dt = base.AccessorProperty(CombinedDatetimelikeProperties)
29322922

29332923
# -------------------------------------------------------------------------
29342924
# Categorical methods
2935-
2936-
def _make_cat_accessor(self):
2937-
if not is_categorical_dtype(self.dtype):
2938-
raise AttributeError("Can only use .cat accessor with a "
2939-
"'category' dtype")
2940-
return CategoricalAccessor(self.values, self.index)
2941-
2942-
cat = base.AccessorProperty(CategoricalAccessor, _make_cat_accessor)
2925+
cat = base.AccessorProperty(CategoricalAccessor)
29432926

29442927
def _dir_deletions(self):
29452928
return self._accessors

pandas/core/strings.py

+15-15
Original file line numberDiff line numberDiff line change
@@ -1890,18 +1890,14 @@ def rindex(self, sub, start=0, end=None):
18901890
docstring=_shared_docs['ismethods'] %
18911891
_shared_docs['isdecimal'])
18921892

1893-
1894-
class StringAccessorMixin(object):
1895-
""" Mixin to add a `.str` acessor to the class."""
1896-
1897-
# string methods
1898-
def _make_str_accessor(self):
1893+
@classmethod
1894+
def _make_accessor(cls, data):
18991895
from pandas.core.index import Index
19001896

1901-
if (isinstance(self, ABCSeries) and
1902-
not ((is_categorical_dtype(self.dtype) and
1903-
is_object_dtype(self.values.categories)) or
1904-
(is_object_dtype(self.dtype)))):
1897+
if (isinstance(data, ABCSeries) and
1898+
not ((is_categorical_dtype(data.dtype) and
1899+
is_object_dtype(data.values.categories)) or
1900+
(is_object_dtype(data.dtype)))):
19051901
# it's neither a string series not a categorical series with
19061902
# strings inside the categories.
19071903
# this really should exclude all series with any non-string values
@@ -1910,23 +1906,27 @@ def _make_str_accessor(self):
19101906
raise AttributeError("Can only use .str accessor with string "
19111907
"values, which use np.object_ dtype in "
19121908
"pandas")
1913-
elif isinstance(self, Index):
1909+
elif isinstance(data, Index):
19141910
# can't use ABCIndex to exclude non-str
19151911

19161912
# see scc/inferrence.pyx which can contain string values
19171913
allowed_types = ('string', 'unicode', 'mixed', 'mixed-integer')
1918-
if self.inferred_type not in allowed_types:
1914+
if data.inferred_type not in allowed_types:
19191915
message = ("Can only use .str accessor with string values "
19201916
"(i.e. inferred_type is 'string', 'unicode' or "
19211917
"'mixed')")
19221918
raise AttributeError(message)
1923-
if self.nlevels > 1:
1919+
if data.nlevels > 1:
19241920
message = ("Can only use .str accessor with Index, not "
19251921
"MultiIndex")
19261922
raise AttributeError(message)
1927-
return StringMethods(self)
1923+
return StringMethods(data)
1924+
1925+
1926+
class StringAccessorMixin(object):
1927+
""" Mixin to add a `.str` acessor to the class."""
19281928

1929-
str = AccessorProperty(StringMethods, _make_str_accessor)
1929+
str = AccessorProperty(StringMethods)
19301930

19311931
def _dir_additions(self):
19321932
return set()

0 commit comments

Comments
 (0)