summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Ganssle <pganssle@users.noreply.github.com>2018-01-16 18:06:31 (GMT)
committerAlexander Belopolsky <abalkin@users.noreply.github.com>2018-01-16 18:06:31 (GMT)
commit9f1b7b93f5f0ef589e7b272e127cacf4ce5d23f1 (patch)
tree60cf60454cad0f99056713510c1c91744d7e7ff8
parent6b5a27975a415108a5eac12ee302bf2b3233f4d4 (diff)
downloadcpython-9f1b7b93f5f0ef589e7b272e127cacf4ce5d23f1.zip
cpython-9f1b7b93f5f0ef589e7b272e127cacf4ce5d23f1.tar.gz
cpython-9f1b7b93f5f0ef589e7b272e127cacf4ce5d23f1.tar.bz2
bpo-32403: Faster date and datetime constructors (#4993)
* Add tests for date subclass alternate constructors * Switch over alternate date constructors to fast path * Switch datetime constructors to fastpath, fix bpo-32404 * Add fast path for datetime in date subclass constructor * Set fold in constructor in datetime.combine * Add news entries.
-rw-r--r--Lib/test/datetimetester.py92
-rw-r--r--Misc/NEWS.d/next/Library/2017-12-23-14-51-46.bpo-32403.CVFapH.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2017-12-23-14-54-05.bpo-32404.yJqtlJ.rst2
-rw-r--r--Modules/_datetimemodule.c120
4 files changed, 171 insertions, 45 deletions
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index 1d0c1c5..e8ed79e 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -1552,6 +1552,50 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase):
self.assertEqual(dt1.toordinal(), dt2.toordinal())
self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
+ def test_subclass_alternate_constructors(self):
+ # Test that alternate constructors call the constructor
+ class DateSubclass(self.theclass):
+ def __new__(cls, *args, **kwargs):
+ result = self.theclass.__new__(cls, *args, **kwargs)
+ result.extra = 7
+
+ return result
+
+ args = (2003, 4, 14)
+ d_ord = 731319 # Equivalent ordinal date
+ d_isoformat = '2003-04-14' # Equivalent isoformat()
+
+ base_d = DateSubclass(*args)
+ self.assertIsInstance(base_d, DateSubclass)
+ self.assertEqual(base_d.extra, 7)
+
+ # Timestamp depends on time zone, so we'll calculate the equivalent here
+ ts = datetime.combine(base_d, time(0)).timestamp()
+
+ test_cases = [
+ ('fromordinal', (d_ord,)),
+ ('fromtimestamp', (ts,)),
+ ('fromisoformat', (d_isoformat,)),
+ ]
+
+ for constr_name, constr_args in test_cases:
+ for base_obj in (DateSubclass, base_d):
+ # Test both the classmethod and method
+ with self.subTest(base_obj_type=type(base_obj),
+ constr_name=constr_name):
+ constr = getattr(base_obj, constr_name)
+
+ dt = constr(*constr_args)
+
+ # Test that it creates the right subclass
+ self.assertIsInstance(dt, DateSubclass)
+
+ # Test that it's equal to the base object
+ self.assertEqual(dt, base_d)
+
+ # Test that it called the constructor
+ self.assertEqual(dt.extra, 7)
+
def test_pickling_subclass_date(self):
args = 6, 7, 23
@@ -2420,6 +2464,54 @@ class TestDateTime(TestDate):
self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
dt1.second - 7)
+ def test_subclass_alternate_constructors_datetime(self):
+ # Test that alternate constructors call the constructor
+ class DateTimeSubclass(self.theclass):
+ def __new__(cls, *args, **kwargs):
+ result = self.theclass.__new__(cls, *args, **kwargs)
+ result.extra = 7
+
+ return result
+
+ args = (2003, 4, 14, 12, 30, 15, 123456)
+ d_isoformat = '2003-04-14T12:30:15.123456' # Equivalent isoformat()
+ utc_ts = 1050323415.123456 # UTC timestamp
+
+ base_d = DateTimeSubclass(*args)
+ self.assertIsInstance(base_d, DateTimeSubclass)
+ self.assertEqual(base_d.extra, 7)
+
+ # Timestamp depends on time zone, so we'll calculate the equivalent here
+ ts = base_d.timestamp()
+
+ test_cases = [
+ ('fromtimestamp', (ts,)),
+ # See https://bugs.python.org/issue32417
+ # ('fromtimestamp', (ts, timezone.utc)),
+ ('utcfromtimestamp', (utc_ts,)),
+ ('fromisoformat', (d_isoformat,)),
+ ('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f')),
+ ('combine', (date(*args[0:3]), time(*args[3:]))),
+ ]
+
+ for constr_name, constr_args in test_cases:
+ for base_obj in (DateTimeSubclass, base_d):
+ # Test both the classmethod and method
+ with self.subTest(base_obj_type=type(base_obj),
+ constr_name=constr_name):
+ constr = getattr(base_obj, constr_name)
+
+ dt = constr(*constr_args)
+
+ # Test that it creates the right subclass
+ self.assertIsInstance(dt, DateTimeSubclass)
+
+ # Test that it's equal to the base object
+ self.assertEqual(dt, base_d.replace(tzinfo=None))
+
+ # Test that it called the constructor
+ self.assertEqual(dt.extra, 7)
+
def test_fromisoformat_datetime(self):
# Test that isoformat() is reversible
base_dates = [
diff --git a/Misc/NEWS.d/next/Library/2017-12-23-14-51-46.bpo-32403.CVFapH.rst b/Misc/NEWS.d/next/Library/2017-12-23-14-51-46.bpo-32403.CVFapH.rst
new file mode 100644
index 0000000..f05d346
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-12-23-14-51-46.bpo-32403.CVFapH.rst
@@ -0,0 +1,2 @@
+Improved speed of :class:`datetime.date` and :class:`datetime.datetime`
+alternate constructors.
diff --git a/Misc/NEWS.d/next/Library/2017-12-23-14-54-05.bpo-32404.yJqtlJ.rst b/Misc/NEWS.d/next/Library/2017-12-23-14-54-05.bpo-32404.yJqtlJ.rst
new file mode 100644
index 0000000..5299820
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-12-23-14-54-05.bpo-32404.yJqtlJ.rst
@@ -0,0 +1,2 @@
+Fix bug where :meth:`datetime.datetime.fromtimestamp` did not call __new__
+in :class:`datetime.datetime` subclasses.
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index e68c7c0..d1f48e5 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -847,6 +847,27 @@ new_date_ex(int year, int month, int day, PyTypeObject *type)
#define new_date(year, month, day) \
new_date_ex(year, month, day, &PyDateTime_DateType)
+// Forward declaration
+static PyObject * new_datetime_ex(int, int, int, int, int, int, int,
+ PyObject*, PyTypeObject*);
+
+/* Create date instance with no range checking, or call subclass constructor */
+static PyObject *
+new_date_subclass_ex(int year, int month, int day, PyObject *cls) {
+ PyObject *result;
+ // We have "fast path" constructors for two subclasses: date and datetime
+ if ((PyTypeObject *)cls == &PyDateTime_DateType) {
+ result = new_date_ex(year, month, day, (PyTypeObject *)cls);
+ } else if ((PyTypeObject *)cls == &PyDateTime_DateTimeType) {
+ result = new_datetime_ex(year, month, day, 0, 0, 0, 0, Py_None,
+ (PyTypeObject *)cls);
+ } else {
+ result = PyObject_CallFunction(cls, "iii", year, month, day);
+ }
+
+ return result;
+}
+
/* Create a datetime instance with no range checking. */
static PyObject *
new_datetime_ex2(int year, int month, int day, int hour, int minute,
@@ -894,6 +915,40 @@ new_datetime_ex(int year, int month, int day, int hour, int minute,
new_datetime_ex2(y, m, d, hh, mm, ss, us, tzinfo, fold, \
&PyDateTime_DateTimeType)
+static PyObject *
+new_datetime_subclass_fold_ex(int year, int month, int day, int hour, int minute,
+ int second, int usecond, PyObject *tzinfo,
+ int fold, PyObject *cls) {
+ PyObject* dt;
+ if ((PyTypeObject*)cls == &PyDateTime_DateTimeType) {
+ // Use the fast path constructor
+ dt = new_datetime(year, month, day, hour, minute, second, usecond,
+ tzinfo, fold);
+ } else {
+ // Subclass
+ dt = PyObject_CallFunction(cls, "iiiiiiiO",
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ usecond,
+ tzinfo);
+ }
+
+ return dt;
+}
+
+static PyObject *
+new_datetime_subclass_ex(int year, int month, int day, int hour, int minute,
+ int second, int usecond, PyObject *tzinfo,
+ PyObject *cls) {
+ return new_datetime_subclass_fold_ex(year, month, day, hour, minute,
+ second, usecond, tzinfo, 0,
+ cls);
+}
+
/* Create a time instance with no range checking. */
static PyObject *
new_time_ex2(int hour, int minute, int second, int usecond,
@@ -2743,10 +2798,10 @@ date_local_from_object(PyObject *cls, PyObject *obj)
if (_PyTime_localtime(t, &tm) != 0)
return NULL;
- return PyObject_CallFunction(cls, "iii",
- tm.tm_year + 1900,
- tm.tm_mon + 1,
- tm.tm_mday);
+ return new_date_subclass_ex(tm.tm_year + 1900,
+ tm.tm_mon + 1,
+ tm.tm_mday,
+ cls);
}
/* Return new date from current time.
@@ -2809,8 +2864,7 @@ date_fromordinal(PyObject *cls, PyObject *args)
">= 1");
else {
ord_to_ymd(ordinal, &year, &month, &day);
- result = PyObject_CallFunction(cls, "iii",
- year, month, day);
+ result = new_date_subclass_ex(year, month, day, cls);
}
}
return result;
@@ -2845,14 +2899,7 @@ date_fromisoformat(PyObject *cls, PyObject *dtstr) {
return NULL;
}
- PyObject *result;
- if ( (PyTypeObject*)cls == &PyDateTime_DateType ) {
- result = new_date_ex(year, month, day, (PyTypeObject*)cls);
- } else {
- result = PyObject_CallFunction(cls, "iii", year, month, day);
- }
-
- return result;
+ return new_date_subclass_ex(year, month, day, cls);
}
@@ -4596,9 +4643,8 @@ datetime_from_timet_and_us(PyObject *cls, TM_FUNC f, time_t timet, int us,
fold = 1;
}
}
- return new_datetime_ex2(year, month, day, hour,
- minute, second, us, tzinfo, fold,
- (PyTypeObject *)cls);
+ return new_datetime_subclass_fold_ex(year, month, day, hour, minute,
+ second, us, tzinfo, fold, cls);
}
/* Internal helper.
@@ -4764,17 +4810,16 @@ datetime_combine(PyObject *cls, PyObject *args, PyObject *kw)
else
tzinfo = Py_None;
}
- result = PyObject_CallFunction(cls, "iiiiiiiO",
- GET_YEAR(date),
- GET_MONTH(date),
- GET_DAY(date),
- TIME_GET_HOUR(time),
- TIME_GET_MINUTE(time),
- TIME_GET_SECOND(time),
- TIME_GET_MICROSECOND(time),
- tzinfo);
- if (result)
- DATE_SET_FOLD(result, TIME_GET_FOLD(time));
+ result = new_datetime_subclass_fold_ex(GET_YEAR(date),
+ GET_MONTH(date),
+ GET_DAY(date),
+ TIME_GET_HOUR(time),
+ TIME_GET_MINUTE(time),
+ TIME_GET_SECOND(time),
+ TIME_GET_MICROSECOND(time),
+ tzinfo,
+ TIME_GET_FOLD(time),
+ cls);
}
return result;
}
@@ -4832,23 +4877,8 @@ datetime_fromisoformat(PyObject* cls, PyObject *dtstr) {
return NULL;
}
- PyObject* dt;
- if ( (PyTypeObject*)cls == &PyDateTime_DateTimeType ) {
- // Use the fast path constructor
- dt = new_datetime(year, month, day, hour, minute, second, microsecond,
- tzinfo, 0);
- } else {
- // Subclass
- dt = PyObject_CallFunction(cls, "iiiiiiiO",
- year,
- month,
- day,
- hour,
- minute,
- second,
- microsecond,
- tzinfo);
- }
+ PyObject *dt = new_datetime_subclass_ex(year, month, day, hour, minute,
+ second, microsecond, tzinfo, cls);
Py_DECREF(tzinfo);
return dt;