diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index f246ebad3aa2c..c9874b4dd03d6 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -505,6 +505,7 @@ ExtensionType Changes - :meth:`Series.astype` and :meth:`DataFrame.astype` now dispatch to :meth:`ExtensionArray.astype` (:issue:`21185:`). - Slicing a single row of a ``DataFrame`` with multiple ExtensionArrays of the same type now preserves the dtype, rather than coercing to object (:issue:`22784`) - Added :meth:`pandas.api.types.register_extension_dtype` to register an extension type with pandas (:issue:`22664`) +- Updated the ``.type`` attribute for ``PeriodDtype``, ``DatetimeTZDtype``, and ``IntervalDtype`` to be instances of the dtype (``Period``, ``Timestamp``, and ``Interval`` respectively) (:issue:`22938`) .. _whatsnew_0240.api.incompatibilities: diff --git a/pandas/core/dtypes/base.py b/pandas/core/dtypes/base.py index db0a917aefb85..b0fa55e346613 100644 --- a/pandas/core/dtypes/base.py +++ b/pandas/core/dtypes/base.py @@ -181,7 +181,9 @@ def type(self): """The scalar type for the array, e.g. ``int`` It's expected ``ExtensionArray[item]`` returns an instance - of ``ExtensionDtype.type`` for scalar ``item``. + of ``ExtensionDtype.type`` for scalar ``item``, assuming + that value is valid (not NA). NA values do not need to be + instances of `type`. """ raise AbstractMethodError(self) diff --git a/pandas/core/dtypes/common.py b/pandas/core/dtypes/common.py index 5f0b71d4505c2..a9fc9d13d4ab3 100644 --- a/pandas/core/dtypes/common.py +++ b/pandas/core/dtypes/common.py @@ -4,12 +4,13 @@ from pandas.compat import (string_types, text_type, binary_type, PY3, PY36) from pandas._libs import algos, lib -from pandas._libs.tslibs import conversion +from pandas._libs.tslibs import conversion, Period, Timestamp +from pandas._libs.interval import Interval from pandas.core.dtypes.dtypes import ( registry, CategoricalDtype, CategoricalDtypeType, DatetimeTZDtype, - DatetimeTZDtypeType, PeriodDtype, PeriodDtypeType, IntervalDtype, - IntervalDtypeType, PandasExtensionDtype, ExtensionDtype, + PeriodDtype, IntervalDtype, + PandasExtensionDtype, ExtensionDtype, _pandas_registry) from pandas.core.dtypes.generic import ( ABCCategorical, ABCPeriodIndex, ABCDatetimeIndex, ABCSeries, @@ -1905,20 +1906,20 @@ def _get_dtype_type(arr_or_dtype): elif isinstance(arr_or_dtype, CategoricalDtype): return CategoricalDtypeType elif isinstance(arr_or_dtype, DatetimeTZDtype): - return DatetimeTZDtypeType + return Timestamp elif isinstance(arr_or_dtype, IntervalDtype): - return IntervalDtypeType + return Interval elif isinstance(arr_or_dtype, PeriodDtype): - return PeriodDtypeType + return Period elif isinstance(arr_or_dtype, string_types): if is_categorical_dtype(arr_or_dtype): return CategoricalDtypeType elif is_datetime64tz_dtype(arr_or_dtype): - return DatetimeTZDtypeType + return Timestamp elif is_period_dtype(arr_or_dtype): - return PeriodDtypeType + return Period elif is_interval_dtype(arr_or_dtype): - return IntervalDtypeType + return Interval return _get_dtype_type(np.dtype(arr_or_dtype)) try: return arr_or_dtype.dtype.type diff --git a/pandas/core/dtypes/dtypes.py b/pandas/core/dtypes/dtypes.py index fe5cc9389a8ba..beda9bc02f4d5 100644 --- a/pandas/core/dtypes/dtypes.py +++ b/pandas/core/dtypes/dtypes.py @@ -4,6 +4,8 @@ import numpy as np from pandas import compat from pandas.core.dtypes.generic import ABCIndexClass, ABCCategoricalIndex +from pandas._libs.tslibs import Period, NaT, Timestamp +from pandas._libs.interval import Interval from .base import ExtensionDtype, _DtypeOpsMixin @@ -469,13 +471,6 @@ def _is_boolean(self): return is_bool_dtype(self.categories) -class DatetimeTZDtypeType(type): - """ - the type of DatetimeTZDtype, this metaclass determines subclass ability - """ - pass - - class DatetimeTZDtype(PandasExtensionDtype): """ @@ -485,7 +480,7 @@ class DatetimeTZDtype(PandasExtensionDtype): THIS IS NOT A REAL NUMPY DTYPE, but essentially a sub-class of np.datetime64[ns] """ - type = DatetimeTZDtypeType + type = Timestamp kind = 'M' str = '|M8[ns]' num = 101 @@ -583,20 +578,13 @@ def __eq__(self, other): str(self.tz) == str(other.tz)) -class PeriodDtypeType(type): - """ - the type of PeriodDtype, this metaclass determines subclass ability - """ - pass - - class PeriodDtype(PandasExtensionDtype): """ A Period duck-typed class, suitable for holding a period with freq dtype. THIS IS NOT A REAL NUMPY DTYPE, but essentially a sub-class of np.int64. """ - type = PeriodDtypeType + type = Period kind = 'O' str = '|O08' base = np.dtype('O') @@ -666,11 +654,15 @@ def construct_from_string(cls, string): raise TypeError("could not construct PeriodDtype") def __unicode__(self): - return "period[{freq}]".format(freq=self.freq.freqstr) + return compat.text_type(self.name) @property def name(self): - return str(self) + return str("period[{freq}]".format(freq=self.freq.freqstr)) + + @property + def na_value(self): + return NaT def __hash__(self): # make myself hashable @@ -705,13 +697,6 @@ def is_dtype(cls, dtype): return super(PeriodDtype, cls).is_dtype(dtype) -class IntervalDtypeType(type): - """ - the type of IntervalDtype, this metaclass determines subclass ability - """ - pass - - @register_extension_dtype class IntervalDtype(PandasExtensionDtype, ExtensionDtype): """ @@ -800,7 +785,6 @@ def construct_from_string(cls, string): @property def type(self): - from pandas import Interval return Interval def __unicode__(self): diff --git a/pandas/tests/dtypes/test_common.py b/pandas/tests/dtypes/test_common.py index a7a9faa9e77eb..f87c51a4ee16b 100644 --- a/pandas/tests/dtypes/test_common.py +++ b/pandas/tests/dtypes/test_common.py @@ -605,15 +605,15 @@ def test__get_dtype_fails(input_param): (pd.DatetimeIndex([1, 2]), np.datetime64), (pd.DatetimeIndex([1, 2]).dtype, np.datetime64), ('