Skip to content

Commit

Permalink
Implement _make_accessor classmethod for PandasDelegate (#17166)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbrockmendel authored and jreback committed Aug 8, 2017
1 parent 5d8319e commit 7bef6d8
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 37 deletions.
11 changes: 9 additions & 2 deletions pandas/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,12 @@ def __setattr__(self, key, value):
class PandasDelegate(PandasObject):
""" an abstract base class for delegating methods/properties """

@classmethod
def _make_accessor(cls, data):
raise AbstractMethodError("_make_accessor should be implemented"
"by subclass and return an instance"
"of `cls`.")

def _delegate_property_get(self, name, *args, **kwargs):
raise TypeError("You cannot access the "
"property {name}".format(name=name))
Expand Down Expand Up @@ -231,9 +237,10 @@ class AccessorProperty(object):
"""Descriptor for implementing accessor properties like Series.str
"""

def __init__(self, accessor_cls, construct_accessor):
def __init__(self, accessor_cls, construct_accessor=None):
self.accessor_cls = accessor_cls
self.construct_accessor = construct_accessor
self.construct_accessor = (construct_accessor or
accessor_cls._make_accessor)
self.__doc__ = accessor_cls.__doc__

def __get__(self, instance, owner=None):
Expand Down
7 changes: 7 additions & 0 deletions pandas/core/categorical.py
Original file line number Diff line number Diff line change
Expand Up @@ -2061,6 +2061,13 @@ def _delegate_method(self, name, *args, **kwargs):
if res is not None:
return Series(res, index=self.index)

@classmethod
def _make_accessor(cls, data):
if not is_categorical_dtype(data.dtype):
raise AttributeError("Can only use .cat accessor with a "
"'category' dtype")
return CategoricalAccessor(data.values, data.index)


CategoricalAccessor._add_delegate_accessors(delegate=Categorical,
accessors=["categories",
Expand Down
8 changes: 8 additions & 0 deletions pandas/core/indexes/accessors.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,11 @@ class CombinedDatetimelikeProperties(DatetimeProperties, TimedeltaProperties):
# the Series.dt class property. For Series objects, .dt will always be one
# of the more specific classes above.
__doc__ = DatetimeProperties.__doc__

@classmethod
def _make_accessor(cls, data):
try:
return maybe_to_datetimelike(data)
except Exception:
raise AttributeError("Can only use .dt accessor with "
"datetimelike values")
23 changes: 3 additions & 20 deletions pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@
from pandas.core.internals import SingleBlockManager
from pandas.core.categorical import Categorical, CategoricalAccessor
import pandas.core.strings as strings
from pandas.core.indexes.accessors import (
maybe_to_datetimelike, CombinedDatetimelikeProperties)
from pandas.core.indexes.accessors import CombinedDatetimelikeProperties
from pandas.core.indexes.datetimes import DatetimeIndex
from pandas.core.indexes.timedeltas import TimedeltaIndex
from pandas.core.indexes.period import PeriodIndex
Expand Down Expand Up @@ -2919,27 +2918,11 @@ def to_period(self, freq=None, copy=True):

# -------------------------------------------------------------------------
# Datetimelike delegation methods

def _make_dt_accessor(self):
try:
return maybe_to_datetimelike(self)
except Exception:
raise AttributeError("Can only use .dt accessor with datetimelike "
"values")

dt = base.AccessorProperty(CombinedDatetimelikeProperties,
_make_dt_accessor)
dt = base.AccessorProperty(CombinedDatetimelikeProperties)

# -------------------------------------------------------------------------
# Categorical methods

def _make_cat_accessor(self):
if not is_categorical_dtype(self.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)
cat = base.AccessorProperty(CategoricalAccessor)

def _dir_deletions(self):
return self._accessors
Expand Down
30 changes: 15 additions & 15 deletions pandas/core/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -1890,18 +1890,14 @@ def rindex(self, sub, start=0, end=None):
docstring=_shared_docs['ismethods'] %
_shared_docs['isdecimal'])


class StringAccessorMixin(object):
""" Mixin to add a `.str` acessor to the class."""

# string methods
def _make_str_accessor(self):
@classmethod
def _make_accessor(cls, data):
from pandas.core.index import Index

if (isinstance(self, ABCSeries) and
not ((is_categorical_dtype(self.dtype) and
is_object_dtype(self.values.categories)) or
(is_object_dtype(self.dtype)))):
if (isinstance(data, ABCSeries) and
not ((is_categorical_dtype(data.dtype) and
is_object_dtype(data.values.categories)) or
(is_object_dtype(data.dtype)))):
# it's neither a string series not a categorical series with
# strings inside the categories.
# this really should exclude all series with any non-string values
Expand All @@ -1910,23 +1906,27 @@ def _make_str_accessor(self):
raise AttributeError("Can only use .str accessor with string "
"values, which use np.object_ dtype in "
"pandas")
elif isinstance(self, Index):
elif isinstance(data, Index):
# can't use ABCIndex to exclude non-str

# see scc/inferrence.pyx which can contain string values
allowed_types = ('string', 'unicode', 'mixed', 'mixed-integer')
if self.inferred_type not in allowed_types:
if data.inferred_type not in allowed_types:
message = ("Can only use .str accessor with string values "
"(i.e. inferred_type is 'string', 'unicode' or "
"'mixed')")
raise AttributeError(message)
if self.nlevels > 1:
if data.nlevels > 1:
message = ("Can only use .str accessor with Index, not "
"MultiIndex")
raise AttributeError(message)
return StringMethods(self)
return StringMethods(data)


class StringAccessorMixin(object):
""" Mixin to add a `.str` acessor to the class."""

str = AccessorProperty(StringMethods, _make_str_accessor)
str = AccessorProperty(StringMethods)

def _dir_additions(self):
return set()
Expand Down

0 comments on commit 7bef6d8

Please sign in to comment.