8000 gh-89039: Call subclass constructors in datetime.*.replace (GH-114780) · python/cpython@46190d9 · GitHub
[go: up one dir, main page]

Skip to content

Commit 46190d9

Browse files
authored
gh-89039: Call subclass constructors in datetime.*.replace (GH-114780)
When replace() method is called on a subclass of datetime, date or time, properly call derived constructor. Previously, only the base class's constructor was called. Also, make sure to pass non-zero fold values when creating subclasses in various methods. Previously, fold was silently ignored.
1 parent 92483b2 commit 46190d9

File tree

3 files changed

+124
-21
lines changed

3 files changed

+124
-21
lines changed

Lib/test/datetimetester.py

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,11 +1723,24 @@ def test_replace(self):
17231723

17241724
def test_subclass_replace(self):
17251725
class DateSubclass(self.theclass):
1726-
pass
1726+
def __new__(cls, *args, **kwargs):
1727+
result = self.theclass.__new__(cls, *args, **kwargs)
1728+
result.extra = 7
1729+
return result
17271730

17281731
dt = DateSubclass(2012, 1, 1)
1729-
self.assertIs(type(dt.replace(year=2013)), DateSubclass)
1730-
self.assertIs(type(copy.replace(dt, year=2013)), DateSubclass)
1732+
1733+
test_cases = [
1734+
('self.replace', dt.replace(year=2013)),
1735+
('copy.replace', copy.replace(dt, year=2013)),
1736+
]
1737+
1738+
for name, res in test_cases:
1739+
with self.subTest(name):
1740+
self.assertIs(type(res), DateSubclass)
1741+
self.assertEqual(res.year, 2013)
1742+
self.assertEqual(res.month, 1)
1743+
self.assertEqual(res.extra, 7)
17311744

17321745
def test_subclass_date(self):
17331746

@@ -3025,6 +3038,26 @@ def __new__(cls, *args, **kwargs):
30253038
self.assertIsInstance(dt, DateTimeSubclass)
30263039
self.assertEqual(dt.extra, 7)
30273040

3041+
def test_subclass_replace_fold(self):
3042+
class DateTimeSubclass(self.theclass):
3043+
pass
3044+
3045+
dt = DateTimeSubclass(2012, 1, 1)
3046+
dt2 = DateTimeSubclass(2012, 1, 1, fold=1)
3047+
3048+
test_cases = [
3049+
('self.replace', dt.replace(year=2013), 0),
3050+
('self.replace', dt2.replace(year=2013), 1),
3051+
('copy.replace', copy.replace(dt, year=2013), 0),
3052+
('copy.replace', copy.replace(dt2, year=2013), 1),
3053+
]
3054+
3055+
for name, res, fold in test_cases:
3056+
with self.subTest(name, fold=fold):
3057+
self.assertIs(type(res), DateTimeSubclass)
3058+
self.assertEqual(res.year, 2013)
3059+
self.assertEqual(res.fold, fold)
3060+
30283061
def test_fromisoformat_datetime(self):
30293062
# Test that isoformat() is reversible
30303063
base_dates = [
@@ -3705,11 +3738,28 @@ def test_replace(self):
37053738

37063739
def test_subclass_replace(self):
37073740
class TimeSubclass(self.theclass):
3708-
pass
3741+
def __new__(cls, *args, **kwargs):
3742+
result = self.theclass.__new__(cls, *args, **kwargs)
3743+
result.extra = 7
3744+
return result
37093745

37103746
ctime = TimeSubclass(12, 30)
3711-
self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
3712-
self.assertIs(type(copy.replace(ctime, hour=10)), TimeSubclass)
3747+
ctime2 = TimeSubclass(12, 30, fold=1)
3748+
3749+
test_cases = [
3750+
('self.replace', ctime.replace(hour=10), 0),
3751+
('self.replace', ctime2.replace(hour=10), 1),
3752+
('copy.replace', copy.replace(ctime, hour=10), 0),
3753+
('copy.replace', copy.replace(ctime2, hour=10), 1),
3754+
]
3755+
3756+
for name, res, fold in test_cases:
3757+
with self.subTest(name, fold=fold):
3758+
self.assertIs(type(res), TimeSubclass)
3759+
self.assertEqual(res.hour, 10)
3760+
self.assertEqual(res.minute, 30)
3761+
self.assertEqual(res.extra, 7)
3762+
self.assertEqual(res.fold, fold)
37133763

37143764
def test_subclass_time(self):
37153765

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
When replace() method is called on a subclass of datetime, date or time,
2+
properly call derived constructor. Previously, only the base class's
3+
constructor was called.
4+
5+
Also, make sure to pass non-zero fold values when creating subclasses in
6+
various methods. Previously, fold was silently ignored.

Modules/_datetimemodule.c

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,40 @@ new_datetime_ex(int year, int month, int day, int hour, int minute,
10451045
new_datetime_ex2(y, m, d, hh, mm, ss, us, tzinfo, fold, \
10461046
&PyDateTime_DateTimeType)
10471047

1048+
static PyObject *
1049+
call_subclass_fold(PyObject *cls, int fold, const char *format, ...)
1050+
{
1051+
PyObject *kwargs = NULL, *res = NULL;
1052+
va_list va;
1053+
1054+
va_start(va, format);
1055+
PyObject *args = Py_VaBuildValue(format, va);
1056+
va_end(va);
1057+
if (args == NULL) {
1058+
return NULL;
1059+
}
1060+
if (fold) {
1061+
kwargs = PyDict_New();
1062+
if (kwargs == NULL) {
1063+
goto Done;
1064+
}
1065+
PyObject *obj = PyLong_FromLong(fold);
1066+
if (obj == NULL) {
1067+
goto Done;
1068+
}
1069+
int err = PyDict_SetItemString(kwargs, "fold", obj);
1070+
Py_DECREF(obj);
1071+
if (err < 0) {
1072+
goto Done;
1073+
}
1074+
}
1075+
res = PyObject_Call(cls, args, kwargs);
1076+
Done:
1077+
Py_DECREF(args);
1078+
Py_XDECREF(kwargs);
1079+
return res;
1080+
}
1081+
10481082
static PyObject *
10491083
new_datetime_subclass_fold_ex(int year, int month, int day, int hour, int minute,
10501084
int second, int usecond, PyObject *tzinfo,
@@ -1054,17 +1088,11 @@ new_datetime_subclass_fold_ex(int year, int month, int day, int hour, int minute
10541088
// Use the fast path constructor
10551089
dt = new_datetime(year, month, day, hour, minute, second, usecond,
10561090
tzinfo, fold);
1057-
} else {
1091+
}
1092+
else {
10581093
// Subclass
1059-
dt = PyObject_CallFunction(cls, "iiiiiiiO",
1060-
year,
1061-
month,
1062-
day,
1063-
hour,
1064-
minute,
1065-
second,
1066-
usecond,
1067-
tzinfo);
1094+
dt = call_subclass_fold(cls, fold, "iiiiiiiO", year, month, day,
1095+
hour, minute, second, usecond, tzinfo);
10681096
}
10691097

10701098
return dt;
@@ -1120,6 +1148,24 @@ new_time_ex(int hour, int minute, int second, int usecond,
11201148
#define new_time(hh, mm, ss, us, tzinfo, fold) \
11211149
new_time_ex2(hh, mm, ss, us, tzinfo, fold, &PyDateTime_TimeType)
11221150

1151+
static PyObject *
1152+
new_time_subclass_fold_ex(int hour, int minute, int second, int usecond,
1153+
PyObject *tzinfo, int fold, PyObject *cls)
1154+
{
1155+
PyObject *t;
1156+
if ((PyTypeObject*)cls == &PyDateTime_TimeType) {
1157+
// Use the fast path constructor
1158+
t = new_time(hour, minute, second, usecond, tzinfo, fold);
1159+
}
1160+
else {
1161+
// Subclass
1162+
t = call_subclass_fold(cls, fold, "iiiiO", hour, minute, second,
1163+
usecond, tzinfo);
1164+
}
1165+
1166+
return t;
1167+
}
1168+
11231169
/* Create a timedelta instance. Normalize the members iff normalize is
11241170
* true. Passing false is a speed optimization, if you know for sure
11251171
* that seconds and microseconds are already in their proper ranges. In any
@@ -3480,7 +3526,7 @@ datetime_date_replace_impl(PyDateTime_Date *self, int year, int month,
34803526
int day)
34813527
/*[clinic end generated code: output=2a9430d1e6318aeb input=0d1f02685b3e90f6]*/
34823528
{
3483-
return new_date_ex(year, month, day, Py_TYPE(self));
3529+
return new_date_subclass_ex(year, month, day, (PyObject *)Py_TYPE(self));
34843530
}
34853531

34863532
static Py_hash_t
@@ -4589,8 +4635,8 @@ datetime_time_replace_impl(PyDateTime_Time *self, int hour, int minute,
45894635
int fold)
45904636
/*[clinic end generated code: output=0b89a44c299e4f80 input=9b6a35b1e704b0ca]*/
45914637
{
4592-
return new_time_ex2(hour, minute, second, microsecond, tzinfo, fold,
4593-
Py_TYPE(self));
4638+
return new_time_subclass_fold_ex(hour, minute, second, microsecond, tzinfo,
4639+
fold, (PyObject *)Py_TYPE(self));
45944640
}
45954641

45964642
static PyObject *
@@ -6039,8 +6085,9 @@ datetime_datetime_replace_impl(PyDateTime_DateTime *self, int year,
60396085
int fold)
60406086
/*[clinic end generated code: output=00bc96536833fddb input=9b38253d56d9bcad]*/
60416087
{
6042-
return new_datetime_ex2(year, month, day, hour, minute, second,
6043-
microsecond, tzinfo, fold, Py_TYPE(self));
6088+
return new_datetime_subclass_fold_ex(year, month, day, hour, minute,
6089+
second, microsecond, tzinfo, fold,
6090+
(PyObject *)Py_TYPE(self));
60446091
}
60456092

60466093
static PyObject *

0 commit comments

Comments
 (0)
0