Skip to content

Commit f44778a

Browse files
committed
convert Period class to Cython extension type
1 parent a346f49 commit f44778a

File tree

2 files changed

+32
-39
lines changed

2 files changed

+32
-39
lines changed

pandas/src/period.pyx

Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
from datetime import datetime, date, timedelta
22
import operator
33

4+
from cpython cimport (
5+
PyObject_RichCompareBool,
6+
Py_EQ, Py_NE,
7+
)
8+
49
from numpy cimport (int8_t, int32_t, int64_t, import_array, ndarray,
510
NPY_INT64, NPY_DATETIME, NPY_TIMEDELTA)
611
import numpy as np
@@ -27,6 +32,7 @@ from tslib cimport (
2732
_is_utc,
2833
_is_tzlocal,
2934
_get_dst_info,
35+
_nat_scalar_rules,
3036
)
3137

3238
from sys import version_info
@@ -615,7 +621,7 @@ def _period_field_accessor(name, alias):
615621
return property(f)
616622

617623

618-
class Period(object):
624+
cdef class Period(object):
619625
"""
620626
Represents an period of time
621627
@@ -634,14 +640,17 @@ class Period(object):
634640
minute : int, default 0
635641
second : int, default 0
636642
"""
637-
__slots__ = ['freq', 'ordinal']
643+
cdef public:
644+
int64_t ordinal
645+
object freq
646+
638647
_comparables = ['name','freqstr']
639648
_typ = 'period'
640649

641650
@classmethod
642651
def _from_ordinal(cls, ordinal, freq):
643652
""" fast creation from an ordinal and freq that are already validated! """
644-
self = object.__new__(cls)
653+
self = Period.__new__(cls)
645654
self.ordinal = ordinal
646655
self.freq = freq
647656
return self
@@ -659,7 +668,6 @@ class Period(object):
659668
self.freq = None
660669

661670
# ordinal is the period offset from the gregorian proleptic epoch
662-
self.ordinal = None
663671

664672
if ordinal is not None and value is not None:
665673
raise ValueError(("Only value or ordinal but not both should be "
@@ -669,26 +677,25 @@ class Period(object):
669677
raise ValueError("Ordinal must be an integer")
670678
if freq is None:
671679
raise ValueError('Must supply freq for ordinal value')
672-
self.ordinal = ordinal
673680

674681
elif value is None:
675682
if freq is None:
676683
raise ValueError("If value is None, freq cannot be None")
677684

678-
self.ordinal = _ordinal_from_fields(year, month, quarter, day,
685+
ordinal = _ordinal_from_fields(year, month, quarter, day,
679686
hour, minute, second, freq)
680687

681688
elif isinstance(value, Period):
682689
other = value
683690
if freq is None or _gfc(freq) == _gfc(other.freq):
684-
self.ordinal = other.ordinal
691+
ordinal = other.ordinal
685692
freq = other.freq
686693
else:
687694
converted = other.asfreq(freq)
688-
self.ordinal = converted.ordinal
695+
ordinal = converted.ordinal
689696

690697
elif lib.is_null_datetimelike(value) or value in tslib._nat_strings:
691-
self.ordinal = tslib.iNaT
698+
ordinal = tslib.iNaT
692699
if freq is None:
693700
raise ValueError("If value is NaT, freq cannot be None "
694701
"because it cannot be inferred")
@@ -722,26 +729,30 @@ class Period(object):
722729
# TODO: Better error message - this is slightly confusing
723730
raise ValueError('Only mult == 1 supported')
724731

725-
if self.ordinal is None:
726-
self.ordinal = period_ordinal(dt.year, dt.month, dt.day,
732+
if ordinal is None:
733+
self.ordinal = get_period_ordinal(dt.year, dt.month, dt.day,
727734
dt.hour, dt.minute, dt.second, dt.microsecond, 0,
728735
base)
736+
else:
737+
self.ordinal = ordinal
729738

730739
self.freq = frequencies._get_freq_str(base)
731740

732-
def __eq__(self, other):
741+
def __richcmp__(self, other, op):
733742
if isinstance(other, Period):
734743
from pandas.tseries.frequencies import get_freq_code as _gfc
735744
if other.freq != self.freq:
736745
raise ValueError("Cannot compare non-conforming periods")
737746
if self.ordinal == tslib.iNaT or other.ordinal == tslib.iNaT:
738-
return False
739-
return (self.ordinal == other.ordinal
740-
and _gfc(self.freq) == _gfc(other.freq))
741-
return NotImplemented
742-
743-
def __ne__(self, other):
744-
return not self == other
747+
return _nat_scalar_rules[op]
748+
return PyObject_RichCompareBool(self.ordinal, other.ordinal, op)
749+
else:
750+
if op == Py_EQ:
751+
return NotImplemented
752+
elif op == Py_NE:
753+
return NotImplemented
754+
raise TypeError('Cannot compare type %r with type %r' %
755+
(type(self).__name__, type(other).__name__))
745756

746757
def __hash__(self):
747758
return hash((self.ordinal, self.freq))
@@ -807,25 +818,6 @@ class Period(object):
807818
else: # pragma: no cover
808819
return NotImplemented
809820

810-
def _comp_method(func, name):
811-
def f(self, other):
812-
if isinstance(other, Period):
813-
if other.freq != self.freq:
814-
raise ValueError("Cannot compare non-conforming periods")
815-
if self.ordinal == tslib.iNaT or other.ordinal == tslib.iNaT:
816-
return False
817-
return func(self.ordinal, other.ordinal)
818-
else:
819-
raise TypeError(other)
820-
821-
f.__name__ = name
822-
return f
823-
824-
__lt__ = _comp_method(operator.lt, '__lt__')
825-
__le__ = _comp_method(operator.le, '__le__')
826-
__gt__ = _comp_method(operator.gt, '__gt__')
827-
__ge__ = _comp_method(operator.ge, '__ge__')
828-
829821
def asfreq(self, freq, how='E'):
830822
"""
831823
Convert Period to desired frequency, either at the start or end of the
@@ -1094,7 +1086,7 @@ def _ordinal_from_fields(year, month, quarter, day, hour, minute,
10941086
if quarter is not None:
10951087
year, month = _quarter_to_myear(year, quarter, freq)
10961088

1097-
return period_ordinal(year, month, day, hour, minute, second, 0, 0, base)
1089+
return get_period_ordinal(year, month, day, hour, minute, second, 0, 0, base)
10981090

10991091

11001092
def _quarter_to_myear(year, quarter, freq):

pandas/tslib.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ cpdef object maybe_get_tz(object)
66
cdef bint _is_utc(object)
77
cdef bint _is_tzlocal(object)
88
cdef object _get_dst_info(object)
9+
cdef bint _nat_scalar_rules[6]

0 commit comments

Comments
 (0)