Skip to content

Commit c7fc55e

Browse files
committed
Convert IsoCalendarDate to static type (3.12)
1 parent a8f76fa commit c7fc55e

4 files changed

Lines changed: 201 additions & 74 deletions

File tree

Lib/test/datetimetester.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7270,8 +7270,60 @@ def test_update_type_cache(self):
72707270
assert isinstance(_datetime.timezone.utc, _datetime.tzinfo)
72717271
del sys.modules['_datetime']
72727272
""")
7273+
res = script_helper.assert_python_ok('-c', script)
7274+
self.assertFalse(res.err)
7275+
7276+
def test_module_free(self):
7277+
script = textwrap.dedent("""
7278+
import sys
7279+
import gc
7280+
import weakref
7281+
ws = weakref.WeakSet()
7282+
for _ in range(3):
7283+
import _datetime
7284+
timedelta = _datetime.timedelta # static type
7285+
ws.add(_datetime)
7286+
del sys.modules['_datetime']
7287+
del _datetime
7288+
gc.collect()
7289+
assert len(ws) == 0
7290+
""")
72737291
script_helper.assert_python_ok('-c', script)
72747292

7293+
@unittest.skipIf(not support.Py_DEBUG, "Debug builds only")
7294+
def test_no_leak(self):
7295+
script = textwrap.dedent("""
7296+
import datetime
7297+
datetime.datetime.strptime('20000101', '%Y%m%d').strftime('%Y%m%d')
7298+
""")
7299+
res = script_helper.assert_python_ok('-X', 'showrefcount', '-c', script)
7300+
self.assertIn(b'[0 refs, 0 blocks]', res.err)
7301+
7302+
def test_static_type_at_shutdown(self):
7303+
# gh-132413
7304+
script = textwrap.dedent("""
7305+
import sys
7306+
import _datetime
7307+
timedelta = _datetime.timedelta
7308+
7309+
def gen():
7310+
try:
7311+
yield
7312+
finally:
7313+
# Exceptions are ignored here
7314+
assert not sys.modules
7315+
td = _datetime.timedelta(days=1)
7316+
assert td.days == 1
7317+
td = timedelta(days=1)
7318+
assert td.days == 1
7319+
assert not sys.modules
7320+
7321+
it = gen()
7322+
next(it)
7323+
""")
7324+
res = script_helper.assert_python_ok('-c', script)
7325+
self.assertFalse(res.err)
7326+
72757327

72767328
def load_tests(loader, standard_tests, pattern):
72777329
standard_tests.addTest(ZoneInfoCompleteTest())

Lib/test/test_embed.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,48 @@ def test_datetime_reset_strptime(self):
440440
out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
441441
self.assertEqual(out, '20000101\n' * INIT_LOOPS)
442442

443+
def test_datetime_capi_type_address(self):
444+
# Check if the C-API types keep their addresses until runtime shutdown
445+
code = textwrap.dedent("""
446+
import _datetime as d
447+
print(
448+
f'{id(d.date)}'
449+
f'{id(d.time)}'
450+
f'{id(d.datetime)}'
451+
f'{id(d.timedelta)}'
452+
f'{id(d.tzinfo)}'
453+
)
454+
""")
455+
out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
456+
self.assertEqual(len(set(out.splitlines())), 1)
457+
458+
def test_datetime_capi_at_shutdown(self):
459+
# gh-132413
460+
code = textwrap.dedent("""
461+
import sys
462+
import _testcapi
463+
_testcapi.test_datetime_capi() # PyDateTime_IMPORT only once
464+
timedelta = _testcapi.get_capi_types()['timedelta']
465+
466+
def gen():
467+
try:
468+
yield
469+
finally:
470+
assert not sys.modules
471+
res = 0
472+
try:
473+
timedelta(days=1)
474+
res = 1
475+
except ImportError:
476+
res = 2
477+
print(res)
478+
479+
it = gen()
480+
next(it)
481+
""")
482+
out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
483+
self.assertEqual(out, '1\n' * INIT_LOOPS)
484+
443485
def test_static_types_inherited_slots(self):
444486
script = textwrap.dedent("""
445487
import test.support

Modules/_datetimemodule.c

Lines changed: 75 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,10 @@ static PyTypeObject PyDateTime_TimeType;
3232
static PyTypeObject PyDateTime_DeltaType;
3333
static PyTypeObject PyDateTime_TZInfoType;
3434
static PyTypeObject PyDateTime_TimeZoneType;
35+
static PyTypeObject PyDateTime_IsoCalendarDateType;
3536

3637

3738
typedef struct {
38-
/* Module heap types. */
39-
PyTypeObject *isocalendar_date_type;
40-
4139
/* Conversion factors. */
4240
PyObject *us_per_ms; // 1_000
4341
PyObject *us_per_second; // 1_000_000
@@ -75,7 +73,7 @@ typedef struct {
7573
#define DELTA_TYPE(st) &PyDateTime_DeltaType
7674
#define TZINFO_TYPE(st) &PyDateTime_TZInfoType
7775
#define TIMEZONE_TYPE(st) &PyDateTime_TimeZoneType
78-
#define ISOCALENDAR_DATE_TYPE(st) st->isocalendar_date_type
76+
#define ISOCALENDAR_DATE_TYPE(st) &PyDateTime_IsoCalendarDateType
7977

8078
#define PyDate_CAST(op) ((PyDateTime_Date *)(op))
8179
#define PyDate_Check(op) PyObject_TypeCheck(op, DATE_TYPE(NO_STATE))
@@ -102,15 +100,52 @@ typedef struct {
102100

103101
#define PyIsoCalendarDate_CAST(op) ((PyDateTime_IsoCalendarDate *)(op))
104102

105-
#define CONST_US_PER_MS(st) st->us_per_ms
106-
#define CONST_US_PER_SECOND(st) st->us_per_second
107-
#define CONST_US_PER_MINUTE(st) st->us_per_minute
108-
#define CONST_US_PER_HOUR(st) st->us_per_hour
109-
#define CONST_US_PER_DAY(st) st->us_per_day
110-
#define CONST_US_PER_WEEK(st) st->us_per_week
111-
#define CONST_SEC_PER_DAY(st) st->seconds_per_day
112-
#define CONST_EPOCH(st) st->epoch
103+
static inline PyObject *
104+
get_const_us_per_ms(datetime_state *st) {
105+
return st ? st->us_per_ms : PyLong_FromLong(1000);
106+
}
107+
108+
static inline PyObject *
109+
get_const_us_per_second(datetime_state *st) {
110+
return st ? st->us_per_second : PyLong_FromLong(1000000);
111+
}
112+
113+
static inline PyObject *
114+
get_const_us_per_minute(datetime_state *st) {
115+
return st ? st->us_per_minute : PyLong_FromLong(60000000);
116+
}
117+
118+
static inline PyObject *
119+
get_const_us_per_hour(datetime_state *st) {
120+
return st ? st->us_per_hour : PyLong_FromDouble(3600000000.0);
121+
}
122+
123+
static inline PyObject *
124+
get_const_us_per_day(datetime_state *st) {
125+
return st ? st->us_per_day : PyLong_FromDouble(86400000000.0);
126+
}
127+
128+
static inline PyObject *
129+
get_const_us_per_week(datetime_state *st) {
130+
return st ? st->us_per_week : PyLong_FromDouble(604800000000.0);
131+
}
132+
133+
static inline PyObject *
134+
get_const_sec_per_day(datetime_state *st) {
135+
return st ? st->seconds_per_day : PyLong_FromLong(24 * 3600);
136+
}
137+
138+
#define CONST_US_PER_MS(st) get_const_us_per_ms(st)
139+
#define CONST_US_PER_SECOND(st) get_const_us_per_second(st)
140+
#define CONST_US_PER_MINUTE(st) get_const_us_per_minute(st)
141+
#define CONST_US_PER_HOUR(st) get_const_us_per_hour(st)
142+
#define CONST_US_PER_DAY(st) get_const_us_per_day(st)
143+
#define CONST_US_PER_WEEK(st) get_const_us_per_week(st)
144+
#define CONST_SEC_PER_DAY(st) get_const_sec_per_day(st)
113145
#define CONST_UTC(st) ((PyObject *)&utc_timezone)
146+
#define CONST_EPOCH(st) \
147+
(st ? ((datetime_state *)st)->epoch \
148+
: new_datetime(1970, 1, 1, 0, 0, 0, 0, (PyObject *)&utc_timezone, 0))
114149

115150
static datetime_state *
116151
get_module_state(PyObject *module)
@@ -173,6 +208,7 @@ _get_current_state(PyObject **p_mod)
173208
* so we must re-import the module. */
174209
mod = PyImport_ImportModule("_datetime");
175210
if (mod == NULL) {
211+
PyErr_Clear();
176212
return NULL;
177213
}
178214
}
@@ -184,7 +220,7 @@ _get_current_state(PyObject **p_mod)
184220
#define GET_CURRENT_STATE(MOD_VAR) \
185221
_get_current_state(&MOD_VAR)
186222
#define RELEASE_CURRENT_STATE(ST_VAR, MOD_VAR) \
187-
Py_DECREF(MOD_VAR)
223+
Py_XDECREF(MOD_VAR)
188224

189225
static int
190226
set_current_module(PyInterpreterState *interp, PyObject *mod)
@@ -3691,40 +3727,19 @@ static PyMethodDef iso_calendar_date_methods[] = {
36913727
{NULL, NULL},
36923728
};
36933729

3694-
static int
3695-
iso_calendar_date_traverse(PyObject *self, visitproc visit, void *arg)
3696-
{
3697-
Py_VISIT(Py_TYPE(self));
3698-
return PyTuple_Type.tp_traverse(self, visit, arg);
3699-
}
3700-
3701-
static void
3702-
iso_calendar_date_dealloc(PyObject *self)
3703-
{
3704-
PyTypeObject *tp = Py_TYPE(self);
3705-
PyTuple_Type.tp_dealloc(self); // delegate GC-untrack as well
3706-
Py_DECREF(tp);
3707-
}
3708-
3709-
static PyType_Slot isocal_slots[] = {
3710-
{Py_tp_repr, iso_calendar_date_repr},
3711-
{Py_tp_doc, (void *)iso_calendar_date__doc__},
3712-
{Py_tp_methods, iso_calendar_date_methods},
3713-
{Py_tp_getset, iso_calendar_date_getset},
3714-
{Py_tp_new, iso_calendar_date_new},
3715-
{Py_tp_dealloc, iso_calendar_date_dealloc},
3716-
{Py_tp_traverse, iso_calendar_date_traverse},
3717-
{0, NULL},
3730+
static PyTypeObject PyDateTime_IsoCalendarDateType = {
3731+
PyVarObject_HEAD_INIT(NULL, 0)
3732+
.tp_name = "datetime.IsoCalendarDate",
3733+
.tp_basicsize = sizeof(PyDateTime_IsoCalendarDate),
3734+
.tp_repr = (reprfunc) iso_calendar_date_repr,
3735+
.tp_flags = Py_TPFLAGS_DEFAULT,
3736+
.tp_doc = iso_calendar_date__doc__,
3737+
.tp_methods = iso_calendar_date_methods,
3738+
.tp_getset = iso_calendar_date_getset,
3739+
// .tp_base = &PyTuple_Type, // filled in PyInit__datetime
3740+
.tp_new = iso_calendar_date_new,
37183741
};
37193742

3720-
static PyType_Spec isocal_spec = {
3721-
.name = "datetime.IsoCalendarDate",
3722-
.basicsize = sizeof(PyDateTime_IsoCalendarDate),
3723-
.flags = (Py_TPFLAGS_DEFAULT |
3724-
Py_TPFLAGS_HAVE_GC |
3725-
Py_TPFLAGS_IMMUTABLETYPE),
3726-
.slots = isocal_slots,
3727-
};
37283743

37293744
/*[clinic input]
37303745
@classmethod
@@ -3776,7 +3791,7 @@ date_isocalendar(PyObject *self, PyObject *Py_UNUSED(dummy))
37763791
PyObject *current_mod = NULL;
37773792
datetime_state *st = GET_CURRENT_STATE(current_mod);
37783793

3779-
PyObject *v = iso_calendar_date_new_impl(ISOCALENDAR_DATE_TYPE(st),
3794+
PyObject *v = iso_calendar_date_new_impl(&PyDateTime_IsoCalendarDateType,
37803795
year, week + 1, day + 1);
37813796
RELEASE_CURRENT_STATE(st, current_mod);
37823797
if (v == NULL) {
@@ -7155,6 +7170,8 @@ static PyTypeObject * const capi_types[] = {
71557170
&PyDateTime_TZInfoType,
71567171
/* Indirectly, via the utc object. */
71577172
&PyDateTime_TimeZoneType,
7173+
/* Not exposed */
7174+
&PyDateTime_IsoCalendarDateType,
71587175
};
71597176

71607177
/* The C-API is process-global. This violates interpreter isolation
@@ -7214,25 +7231,10 @@ create_timezone_from_delta(int days, int sec, int ms, int normalize)
72147231
static int
72157232
init_state(datetime_state *st, PyObject *module, PyObject *old_module)
72167233
{
7217-
/* Each module gets its own heap types. */
7218-
#define ADD_TYPE(FIELD, SPEC, BASE) \
7219-
do { \
7220-
PyObject *cls = PyType_FromModuleAndSpec( \
7221-
module, SPEC, (PyObject *)BASE); \
7222-
if (cls == NULL) { \
7223-
return -1; \
7224-
} \
7225-
st->FIELD = (PyTypeObject *)cls; \
7226-
} while (0)
7227-
7228-
ADD_TYPE(isocalendar_date_type, &isocal_spec, &PyTuple_Type);
7229-
#undef ADD_TYPE
7230-
72317234
if (old_module != NULL) {
72327235
assert(old_module != module);
72337236
datetime_state *st_old = get_module_state(old_module);
72347237
*st = (datetime_state){
7235-
.isocalendar_date_type = st->isocalendar_date_type,
72367238
.us_per_ms = Py_NewRef(st_old->us_per_ms),
72377239
.us_per_second = Py_NewRef(st_old->us_per_second),
72387240
.us_per_minute = Py_NewRef(st_old->us_per_minute),
@@ -7245,42 +7247,41 @@ init_state(datetime_state *st, PyObject *module, PyObject *old_module)
72457247
return 0;
72467248
}
72477249

7248-
st->us_per_ms = PyLong_FromLong(1000);
7250+
st->us_per_ms = CONST_US_PER_MS(NULL);
72497251
if (st->us_per_ms == NULL) {
72507252
return -1;
72517253
}
7252-
st->us_per_second = PyLong_FromLong(1000000);
7254+
st->us_per_second = CONST_US_PER_SECOND(NULL);
72537255
if (st->us_per_second == NULL) {
72547256
return -1;
72557257
}
7256-
st->us_per_minute = PyLong_FromLong(60000000);
7258+
st->us_per_minute = CONST_US_PER_MINUTE(NULL);
72577259
if (st->us_per_minute == NULL) {
72587260
return -1;
72597261
}
7260-
st->seconds_per_day = PyLong_FromLong(24 * 3600);
7262+
st->seconds_per_day = CONST_SEC_PER_DAY(NULL);
72617263
if (st->seconds_per_day == NULL) {
72627264
return -1;
72637265
}
72647266

72657267
/* The rest are too big for 32-bit ints, but even
72667268
* us_per_week fits in 40 bits, so doubles should be exact.
72677269
*/
7268-
st->us_per_hour = PyLong_FromDouble(3600000000.0);
7270+
st->us_per_hour = CONST_US_PER_HOUR(NULL);
72697271
if (st->us_per_hour == NULL) {
72707272
return -1;
72717273
}
7272-
st->us_per_day = PyLong_FromDouble(86400000000.0);
7274+
st->us_per_day = CONST_US_PER_DAY(NULL);
72737275
if (st->us_per_day == NULL) {
72747276
return -1;
72757277
}
7276-
st->us_per_week = PyLong_FromDouble(604800000000.0);
7278+
st->us_per_week = CONST_US_PER_WEEK(NULL);
72777279
if (st->us_per_week == NULL) {
72787280
return -1;
72797281
}
72807282

72817283
/* Init Unix epoch */
7282-
st->epoch = new_datetime(
7283-
1970, 1, 1, 0, 0, 0, 0, (PyObject *)&utc_timezone, 0);
7284+
st->epoch = CONST_EPOCH(NULL);
72847285
if (st->epoch == NULL) {
72857286
return -1;
72867287
}
@@ -7291,16 +7292,12 @@ init_state(datetime_state *st, PyObject *module, PyObject *old_module)
72917292
static int
72927293
traverse_state(datetime_state *st, visitproc visit, void *arg)
72937294
{
7294-
/* heap types */
7295-
Py_VISIT(st->isocalendar_date_type);
7296-
72977295
return 0;
72987296
}
72997297

73007298
static int
73017299
clear_state(datetime_state *st)
73027300
{
7303-
Py_CLEAR(st->isocalendar_date_type);
73047301
Py_CLEAR(st->us_per_ms);
73057302
Py_CLEAR(st->us_per_second);
73067303
Py_CLEAR(st->us_per_minute);
@@ -7323,6 +7320,7 @@ init_static_types(PyInterpreterState *interp, int reloading)
73237320
// `&...` is not a constant expression according to a strict reading
73247321
// of C standards. Fill tp_base at run-time rather than statically.
73257322
// See https://bugs.python.org/issue40777
7323+
PyDateTime_IsoCalendarDateType.tp_base = &PyTuple_Type;
73267324
PyDateTime_TimeZoneType.tp_base = &PyDateTime_TZInfoType;
73277325
PyDateTime_DateTimeType.tp_base = &PyDateTime_DateType;
73287326

@@ -7369,6 +7367,9 @@ _datetime_exec(PyObject *module)
73697367

73707368
for (size_t i = 0; i < Py_ARRAY_LENGTH(capi_types); i++) {
73717369
PyTypeObject *type = capi_types[i];
7370+
if (type == &PyDateTime_IsoCalendarDateType) {
7371+
continue;
7372+
}
73727373
const char *name = _PyType_Name(type);
73737374
assert(name != NULL);
73747375
if (PyModule_AddObjectRef(module, name, (PyObject *)type) < 0) {

0 commit comments

Comments
 (0)