Author: Matti Picus <matti.pi...@gmail.com> Branch: Changeset: r93684:8a1cb5467f77 Date: 2018-01-19 11:10 +0200 http://bitbucket.org/pypy/pypy/changeset/8a1cb5467f77/
Log: merge cpyext-datetime2 which adds a tzinfo field to datetime C-API types diff --git a/lib_pypy/datetime.py b/lib_pypy/datetime.py --- a/lib_pypy/datetime.py +++ b/lib_pypy/datetime.py @@ -17,10 +17,13 @@ """ from __future__ import division -import time as _time +import time as _timemodule import math as _math import struct as _struct +# for cpyext, use these as base classes +from __pypy__._pypydatetime import dateinterop, deltainterop, timeinterop + _SENTINEL = object() def _cmp(x, y): @@ -179,7 +182,7 @@ def _build_struct_time(y, m, d, hh, mm, ss, dstflag): wday = (_ymd2ord(y, m, d) + 6) % 7 dnum = _days_before_month(y, m) + d - return _time.struct_time((y, m, d, hh, mm, ss, wday, dnum, dstflag)) + return _timemodule.struct_time((y, m, d, hh, mm, ss, wday, dnum, dstflag)) def _format_time(hh, mm, ss, us): # Skip trailing microseconds when us==0. @@ -247,7 +250,7 @@ else: push(ch) newformat = "".join(newformat) - return _time.strftime(newformat, timetuple) + return _timemodule.strftime(newformat, timetuple) # Just raise TypeError if the arg isn't None or a string. def _check_tzname(name): @@ -433,7 +436,7 @@ raise TypeError("unsupported type for timedelta %s component: %s" % (tag, type(num))) -class timedelta(object): +class timedelta(deltainterop): """Represent the difference between two datetime objects. Supported operators: @@ -489,7 +492,7 @@ if not -_MAX_DELTA_DAYS <= d <= _MAX_DELTA_DAYS: raise OverflowError("days=%d; must have magnitude <= %d" % (d, _MAX_DELTA_DAYS)) - self = object.__new__(cls) + self = deltainterop.__new__(cls) self._days = d self._seconds = s self._microseconds = us @@ -667,7 +670,7 @@ timedelta.max = timedelta(_MAX_DELTA_DAYS, 24*3600-1, 1000000-1) timedelta.resolution = timedelta(microseconds=1) -class date(object): +class date(dateinterop): """Concrete date type. Constructors: @@ -707,12 +710,12 @@ if month is None and isinstance(year, bytes) and len(year) == 4 and \ 1 <= ord(year[2]) <= 12: # Pickle support - self = object.__new__(cls) + self = dateinterop.__new__(cls) self.__setstate(year) self._hashcode = -1 return self year, month, day = _check_date_fields(year, month, day) - self = object.__new__(cls) + self = dateinterop.__new__(cls) self._year = year self._month = month self._day = day @@ -724,13 +727,13 @@ @classmethod def fromtimestamp(cls, t): "Construct a date from a POSIX timestamp (like time.time())." - y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t) + y, m, d, hh, mm, ss, weekday, jday, dst = _timemodule.localtime(t) return cls(y, m, d) @classmethod def today(cls): "Construct a date from time.time()." - t = _time.time() + t = _timemodule.time() return cls.fromtimestamp(t) @classmethod @@ -1061,7 +1064,7 @@ _tzinfo_class = tzinfo -class time(object): +class time(timeinterop): """Time with time zone. Constructors: @@ -1097,14 +1100,14 @@ """ if isinstance(hour, bytes) and len(hour) == 6 and ord(hour[0]) < 24: # Pickle support - self = object.__new__(cls) + self = timeinterop.__new__(cls) self.__setstate(hour, minute or None) self._hashcode = -1 return self hour, minute, second, microsecond = _check_time_fields( hour, minute, second, microsecond) _check_tzinfo_arg(tzinfo) - self = object.__new__(cls) + self = timeinterop.__new__(cls) self._hour = hour self._minute = minute self._second = second @@ -1408,7 +1411,7 @@ if isinstance(year, bytes) and len(year) == 10 and \ 1 <= ord(year[2]) <= 12: # Pickle support - self = object.__new__(cls) + self = dateinterop.__new__(cls) self.__setstate(year, month) self._hashcode = -1 return self @@ -1416,7 +1419,7 @@ hour, minute, second, microsecond = _check_time_fields( hour, minute, second, microsecond) _check_tzinfo_arg(tzinfo) - self = object.__new__(cls) + self = dateinterop.__new__(cls) self._year = year self._month = month self._day = day @@ -1461,7 +1464,7 @@ A timezone info object may be passed in as well. """ _check_tzinfo_arg(tz) - converter = _time.localtime if tz is None else _time.gmtime + converter = _timemodule.localtime if tz is None else _timemodule.gmtime self = cls._from_timestamp(converter, timestamp, tz) if tz is not None: self = tz.fromutc(self) @@ -1470,7 +1473,7 @@ @classmethod def utcfromtimestamp(cls, t): "Construct a UTC datetime from a POSIX timestamp (like time.time())." - return cls._from_timestamp(_time.gmtime, t, None) + return cls._from_timestamp(_timemodule.gmtime, t, None) @classmethod def _from_timestamp(cls, converter, timestamp, tzinfo): @@ -1493,13 +1496,13 @@ @classmethod def now(cls, tz=None): "Construct a datetime from time.time() and optional time zone info." - t = _time.time() + t = _timemodule.time() return cls.fromtimestamp(t, tz) @classmethod def utcnow(cls): "Construct a UTC datetime from time.time()." - t = _time.time() + t = _timemodule.time() return cls.utcfromtimestamp(t) @classmethod diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst --- a/pypy/doc/whatsnew-head.rst +++ b/pypy/doc/whatsnew-head.rst @@ -10,3 +10,7 @@ Big refactoring of some cpyext code, which avoids a lot of nonsense when calling C from Python and vice-versa: the result is a big speedup in function/method calls, up to 6 times faster. + +.. branch: cpyext-datetime2 + +Support ``tzinfo`` field on C-API datetime objects, fixes latest pandas HEAD diff --git a/pypy/module/__pypy__/__init__.py b/pypy/module/__pypy__/__init__.py --- a/pypy/module/__pypy__/__init__.py +++ b/pypy/module/__pypy__/__init__.py @@ -58,6 +58,14 @@ } +class PyPyDateTime(MixedModule): + appleveldefs = {} + interpleveldefs = { + 'dateinterop': 'interp_pypydatetime.W_DateTime_Date', + 'timeinterop' : 'interp_pypydatetime.W_DateTime_Time', + 'deltainterop' : 'interp_pypydatetime.W_DateTime_Delta', + } + class Module(MixedModule): """ PyPy specific "magic" functions. A lot of them are experimental and subject to change, many are internal. """ @@ -108,6 +116,7 @@ "thread": ThreadModule, "intop": IntOpModule, "os": OsModule, + '_pypydatetime': PyPyDateTime, } def setup_after_space_initialization(self): diff --git a/pypy/module/__pypy__/interp_pypydatetime.py b/pypy/module/__pypy__/interp_pypydatetime.py new file mode 100644 --- /dev/null +++ b/pypy/module/__pypy__/interp_pypydatetime.py @@ -0,0 +1,24 @@ +from pypy.interpreter.baseobjspace import W_Root +from pypy.interpreter.typedef import TypeDef +from pypy.interpreter.gateway import interp2app +from rpython.tool.sourcetools import func_with_new_name + +def create_class(name): + class W_Class(W_Root): + 'builtin base clasee for datetime.%s to allow interop with cpyext' % name + def descr_new__(space, w_type): + return space.allocate_instance(W_Class, w_type) + + W_Class.typedef = TypeDef(name, + __new__ = interp2app(func_with_new_name( + W_Class.descr_new__.im_func, + '%s_new' % (name,))), + ) + W_Class.typedef.acceptable_as_base_class = True + return W_Class + +W_DateTime_Time = create_class('pypydatetime_time') +W_DateTime_Date = create_class('pypydatetime_date') +W_DateTime_Delta = create_class('pypydatetime_delta') + + diff --git a/pypy/module/cpyext/cdatetime.py b/pypy/module/cpyext/cdatetime.py --- a/pypy/module/cpyext/cdatetime.py +++ b/pypy/module/cpyext/cdatetime.py @@ -1,43 +1,28 @@ from rpython.rtyper.lltypesystem import rffi, lltype from rpython.rtyper.annlowlevel import llhelper -from pypy.module.cpyext.pyobject import PyObject, make_ref +from rpython.rlib.rarithmetic import widen +from pypy.module.cpyext.pyobject import (PyObject, make_ref, make_typedescr, + decref) from pypy.module.cpyext.api import (cpython_api, CANNOT_FAIL, cpython_struct, - PyObjectFields) + PyObjectFields, cts, parse_dir, bootstrap_function, slot_function) from pypy.module.cpyext.import_ import PyImport_Import from pypy.module.cpyext.typeobject import PyTypeObjectPtr from pypy.interpreter.error import OperationError +from pypy.module.__pypy__.interp_pypydatetime import (W_DateTime_Date, + W_DateTime_Time, W_DateTime_Delta) from rpython.tool.sourcetools import func_renamer -# API import function +cts.parse_header(parse_dir / 'cpyext_datetime.h') -PyDateTime_CAPI = cpython_struct( - 'PyDateTime_CAPI', - (('DateType', PyTypeObjectPtr), - ('DateTimeType', PyTypeObjectPtr), - ('TimeType', PyTypeObjectPtr), - ('DeltaType', PyTypeObjectPtr), - ('TZInfoType', PyTypeObjectPtr), - ('Date_FromDate', lltype.Ptr(lltype.FuncType( - [rffi.INT_real, rffi.INT_real, rffi.INT_real, PyTypeObjectPtr], - PyObject))), - ('Time_FromTime', lltype.Ptr(lltype.FuncType( - [rffi.INT_real, rffi.INT_real, rffi.INT_real, rffi.INT_real, - PyObject, PyTypeObjectPtr], - PyObject))), - ('DateTime_FromDateAndTime', lltype.Ptr(lltype.FuncType( - [rffi.INT_real, rffi.INT_real, rffi.INT_real, - rffi.INT_real, rffi.INT_real, rffi.INT_real, rffi.INT_real, - PyObject, PyTypeObjectPtr], - PyObject))), - ('Delta_FromDelta', lltype.Ptr(lltype.FuncType( - [rffi.INT_real, rffi.INT_real, rffi.INT_real, rffi.INT_real, - PyTypeObjectPtr], - PyObject))), - )) +PyDateTime_CAPI = cts.gettype('PyDateTime_CAPI') + +datetimeAPI_global = [] @cpython_api([], lltype.Ptr(PyDateTime_CAPI)) def _PyDateTime_Import(space): + if len(datetimeAPI_global) >0: + return datetimeAPI_global[0] datetimeAPI = lltype.malloc(PyDateTime_CAPI, flavor='raw', track_allocation=False) @@ -76,21 +61,13 @@ _PyDelta_FromDelta.api_func.functype, _PyDelta_FromDelta.api_func.get_wrapper(space)) + datetimeAPI_global.append(datetimeAPI) return datetimeAPI -PyDateTime_DateStruct = lltype.ForwardReference() -PyDateTime_TimeStruct = lltype.ForwardReference() -PyDateTime_DateTimeStruct = lltype.ForwardReference() -cpython_struct("PyDateTime_Date", PyObjectFields, PyDateTime_DateStruct) -PyDateTime_Date = lltype.Ptr(PyDateTime_DateStruct) -cpython_struct("PyDateTime_Time", PyObjectFields, PyDateTime_TimeStruct) -PyDateTime_Time = lltype.Ptr(PyDateTime_TimeStruct) -cpython_struct("PyDateTime_DateTime", PyObjectFields, PyDateTime_DateTimeStruct) -PyDateTime_DateTime = lltype.Ptr(PyDateTime_DateTimeStruct) - -PyDeltaObjectStruct = lltype.ForwardReference() -cpython_struct("PyDateTime_Delta", PyObjectFields, PyDeltaObjectStruct) -PyDateTime_Delta = lltype.Ptr(PyDeltaObjectStruct) +PyDateTime_Time = cts.gettype('PyDateTime_Time*') +PyDateTime_DateTime = cts.gettype('PyDateTime_DateTime*') +PyDateTime_Date = cts.gettype('PyDateTime_Date*') +PyDateTime_Delta = cts.gettype('PyDateTime_Delta*') # Check functions @@ -102,6 +79,8 @@ return space.is_true( space.appexec([w_obj], """(obj): from datetime import %s as datatype + if not isinstance(obj, datatype): + print datatype return isinstance(obj, datatype) """ % (type_name,))) except OperationError: @@ -129,6 +108,72 @@ PyTZInfo_Check, PyTZInfo_CheckExact = make_check_function( "PyTZInfo_Check", "tzinfo") +@bootstrap_function +def init_datetime(space): + # no realize functions since there are no getters + make_typedescr(W_DateTime_Time.typedef, + basestruct=PyDateTime_Time.TO, + attach=type_attach, + dealloc=type_dealloc, + ) + + # why do we need date_dealloc? Since W_DateTime_Date is the base class for + # app level datetime.date. If a c-extension class uses datetime.date for its + # base class and defines a tp_dealloc, we will get this: + # c_class->tp_dealloc == tp_dealloc_func + # c_class->tp_base == datetime.date, + # datetime.date->tp_dealloc = _PyPy_subtype_dealloc + # datetime.date->tp_base = W_DateTime_Date + # W_DateTime_Date->tp_dealloc = _PyPy_subtype_dealloc + # but _PyPy_subtype_dealloc will call tp_dealloc_func, which can call its + # base's tp_dealloc and we get recursion. So break the recursion by setting + # W_DateTime_Date->tp_dealloc + make_typedescr(W_DateTime_Date.typedef, + basestruct=PyDateTime_DateTime.TO, + dealloc=date_dealloc, + ) + + make_typedescr(W_DateTime_Delta.typedef, + basestruct=PyDateTime_Delta.TO, + attach=timedeltatype_attach, + ) + +def type_attach(space, py_obj, w_obj, w_userdata=None): + '''Fills a newly allocated py_obj from the w_obj + Can be called with a datetime, or a time + ''' + py_datetime = rffi.cast(PyDateTime_Time, py_obj) + w_tzinfo = space.getattr(w_obj, space.newtext('tzinfo')) + if space.is_none(w_tzinfo): + py_datetime.c_hastzinfo = cts.cast('unsigned char', 0) + py_datetime.c_tzinfo = lltype.nullptr(PyObject.TO) + else: + py_datetime.c_hastzinfo = cts.cast('unsigned char', 1) + py_datetime.c_tzinfo = make_ref(space, w_tzinfo) + +@slot_function([PyObject], lltype.Void) +def type_dealloc(space, py_obj): + py_datetime = rffi.cast(PyDateTime_Time, py_obj) + if (widen(py_datetime.c_hastzinfo) != 0): + decref(space, py_datetime.c_tzinfo) + from pypy.module.cpyext.object import _dealloc + _dealloc(space, py_obj) + +@slot_function([PyObject], lltype.Void) +def date_dealloc(space, py_obj): + from pypy.module.cpyext.object import _dealloc + _dealloc(space, py_obj) + +def timedeltatype_attach(space, py_obj, w_obj, w_userdata=None): + "Fills a newly allocated py_obj from the w_obj" + py_delta = rffi.cast(PyDateTime_Delta, py_obj) + days = space.int_w(space.getattr(w_obj, space.newtext('days'))) + py_delta.c_days = cts.cast('int', days) + seconds = space.int_w(space.getattr(w_obj, space.newtext('seconds'))) + py_delta.c_seconds = cts.cast('int', seconds) + microseconds = space.int_w(space.getattr(w_obj, space.newtext('microseconds'))) + py_delta.c_microseconds = cts.cast('int', microseconds) + # Constructors. They are better used as macros. @cpython_api([rffi.INT_real, rffi.INT_real, rffi.INT_real, PyTypeObjectPtr], diff --git a/pypy/module/cpyext/include/datetime.h b/pypy/module/cpyext/include/datetime.h --- a/pypy/module/cpyext/include/datetime.h +++ b/pypy/module/cpyext/include/datetime.h @@ -4,49 +4,11 @@ extern "C" { #endif -/* Define structure for C API. */ -typedef struct { - /* type objects */ - PyTypeObject *DateType; - PyTypeObject *DateTimeType; - PyTypeObject *TimeType; - PyTypeObject *DeltaType; - PyTypeObject *TZInfoType; - - /* constructors */ - PyObject *(*Date_FromDate)(int, int, int, PyTypeObject*); - PyObject *(*DateTime_FromDateAndTime)(int, int, int, int, int, int, int, - PyObject*, PyTypeObject*); - PyObject *(*Time_FromTime)(int, int, int, int, PyObject*, PyTypeObject*); - PyObject *(*Delta_FromDelta)(int, int, int, int, PyTypeObject*); -} PyDateTime_CAPI; +#include "cpyext_datetime.h" PyAPI_DATA(PyDateTime_CAPI*) PyDateTimeAPI; -#define PyDateTime_IMPORT \ - do { \ - if(PyDateTimeAPI==NULL) \ - PyDateTimeAPI = _PyDateTime_Import(); \ - } while (0) -typedef struct { - PyObject_HEAD -} PyDateTime_Delta; - -typedef struct { - PyObject_HEAD -} PyDateTime_Date; - -typedef struct { - PyObject_HEAD -} PyDateTime_Time; - -typedef struct { - PyObject_HEAD -} PyDateTime_DateTime; - -typedef struct { - PyObject_HEAD -} PyDateTime_TZInfo; +#define PyDateTime_IMPORT (PyDateTimeAPI = _PyDateTime_Import()) /* Macros for accessing constructors in a simplified fashion. */ #define PyDate_FromDate(year, month, day) \ diff --git a/pypy/module/cpyext/parse/cpyext_datetime.h b/pypy/module/cpyext/parse/cpyext_datetime.h new file mode 100644 --- /dev/null +++ b/pypy/module/cpyext/parse/cpyext_datetime.h @@ -0,0 +1,52 @@ +/* Define structure for C API. */ +typedef struct { + /* type objects */ + PyTypeObject *DateType; + PyTypeObject *DateTimeType; + PyTypeObject *TimeType; + PyTypeObject *DeltaType; + PyTypeObject *TZInfoType; + + /* constructors */ + PyObject *(*Date_FromDate)(int, int, int, PyTypeObject*); + PyObject *(*DateTime_FromDateAndTime)(int, int, int, int, int, int, int, + PyObject*, PyTypeObject*); + PyObject *(*Time_FromTime)(int, int, int, int, PyObject*, PyTypeObject*); + PyObject *(*Delta_FromDelta)(int, int, int, int, PyTypeObject*); +} PyDateTime_CAPI; + +typedef struct +{ + PyObject_HEAD + int days; /* -MAX_DELTA_DAYS <= days <= MAX_DELTA_DAYS */ + int seconds; /* 0 <= seconds < 24*3600 is invariant */ + int microseconds; /* 0 <= microseconds < 1000000 is invariant */ +} PyDateTime_Delta; + +/* The datetime and time types have an optional tzinfo member, + * PyNone if hastzinfo is false. + */ +typedef struct +{ + PyObject_HEAD + unsigned char hastzinfo; + PyObject *tzinfo; +} PyDateTime_Time; + +typedef struct +{ + PyObject_HEAD + unsigned char hastzinfo; + PyObject *tzinfo; +} PyDateTime_DateTime; + + +typedef struct { + PyObject_HEAD +} PyDateTime_Date; + + +typedef struct { + PyObject_HEAD +} PyDateTime_TZInfo; + _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit