Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement _make_accessor classmethod for PandasDelegate #17166

Merged
merged 2 commits into from
Aug 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -55,8 +55,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 @@ -2912,27 +2911,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