gh-71587: Establish global state in `_datetime` (#110475)

* Use explicit initialiser for m_base
* Add module state stub; establish global state on stack
* Put conversion factors in state struct
* Move PyDateTime_TimeZone_UTC to state
* Move PyDateTime_Epoch to state struct
* Fix ref leaks in and clean up initialisation
This commit is contained in:
Erlend E. Aasland 2023-10-12 10:28:10 +02:00 committed by GitHub
parent ea530f2f9a
commit ec5622d197
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 173 additions and 145 deletions

View File

@ -40,6 +40,27 @@
#define PyTimezone_Check(op) PyObject_TypeCheck(op, &PyDateTime_TimeZoneType) #define PyTimezone_Check(op) PyObject_TypeCheck(op, &PyDateTime_TimeZoneType)
typedef struct {
/* Conversion factors. */
PyObject *us_per_ms; // 1_000
PyObject *us_per_second; // 1_000_000
PyObject *us_per_minute; // 1e6 * 60 as Python int
PyObject *us_per_hour; // 1e6 * 3600 as Python int
PyObject *us_per_day; // 1e6 * 3600 * 24 as Python int
PyObject *us_per_week; // 1e6 * 3600 * 24 * 7 as Python int
PyObject *seconds_per_day; // 3600 * 24 as Python int
/* The interned UTC timezone instance */
PyObject *utc;
/* The interned Unix epoch datetime instance */
PyObject *epoch;
} datetime_state;
static datetime_state _datetime_global_state;
#define STATIC_STATE() (&_datetime_global_state)
/*[clinic input] /*[clinic input]
module datetime module datetime
class datetime.datetime "PyDateTime_DateTime *" "&PyDateTime_DateTimeType" class datetime.datetime "PyDateTime_DateTime *" "&PyDateTime_DateTimeType"
@ -1139,11 +1160,6 @@ typedef struct
PyObject *name; PyObject *name;
} PyDateTime_TimeZone; } PyDateTime_TimeZone;
/* The interned UTC timezone instance */
static PyObject *PyDateTime_TimeZone_UTC;
/* The interned Epoch datetime instance */
static PyObject *PyDateTime_Epoch;
/* Create new timezone instance checking offset range. This /* Create new timezone instance checking offset range. This
function does not check the name argument. Caller must assure function does not check the name argument. Caller must assure
that offset is a timedelta instance and name is either NULL that offset is a timedelta instance and name is either NULL
@ -1177,7 +1193,8 @@ new_timezone(PyObject *offset, PyObject *name)
assert(name == NULL || PyUnicode_Check(name)); assert(name == NULL || PyUnicode_Check(name));
if (name == NULL && delta_bool((PyDateTime_Delta *)offset) == 0) { if (name == NULL && delta_bool((PyDateTime_Delta *)offset) == 0) {
return Py_NewRef(PyDateTime_TimeZone_UTC); datetime_state *st = STATIC_STATE();
return Py_NewRef(st->utc);
} }
if ((GET_TD_DAYS(offset) == -1 && if ((GET_TD_DAYS(offset) == -1 &&
GET_TD_SECONDS(offset) == 0 && GET_TD_SECONDS(offset) == 0 &&
@ -1390,7 +1407,8 @@ tzinfo_from_isoformat_results(int rv, int tzoffset, int tz_useconds)
if (rv == 1) { if (rv == 1) {
// Create a timezone from offset in seconds (0 returns UTC) // Create a timezone from offset in seconds (0 returns UTC)
if (tzoffset == 0) { if (tzoffset == 0) {
return Py_NewRef(PyDateTime_TimeZone_UTC); datetime_state *st = STATIC_STATE();
return Py_NewRef(st->utc);
} }
PyObject *delta = new_delta(0, tzoffset, tz_useconds, 1); PyObject *delta = new_delta(0, tzoffset, tz_useconds, 1);
@ -1807,19 +1825,6 @@ cmperror(PyObject *a, PyObject *b)
return NULL; return NULL;
} }
/* ---------------------------------------------------------------------------
* Cached Python objects; these are set by the module init function.
*/
/* Conversion factors. */
static PyObject *us_per_ms = NULL; /* 1000 */
static PyObject *us_per_second = NULL; /* 1000000 */
static PyObject *us_per_minute = NULL; /* 1e6 * 60 as Python int */
static PyObject *us_per_hour = NULL; /* 1e6 * 3600 as Python int */
static PyObject *us_per_day = NULL; /* 1e6 * 3600 * 24 as Python int */
static PyObject *us_per_week = NULL; /* 1e6*3600*24*7 as Python int */
static PyObject *seconds_per_day = NULL; /* 3600*24 as Python int */
/* --------------------------------------------------------------------------- /* ---------------------------------------------------------------------------
* Class implementations. * Class implementations.
*/ */
@ -1845,7 +1850,8 @@ delta_to_microseconds(PyDateTime_Delta *self)
x1 = PyLong_FromLong(GET_TD_DAYS(self)); x1 = PyLong_FromLong(GET_TD_DAYS(self));
if (x1 == NULL) if (x1 == NULL)
goto Done; goto Done;
x2 = PyNumber_Multiply(x1, seconds_per_day); /* days in seconds */ datetime_state *st = STATIC_STATE();
x2 = PyNumber_Multiply(x1, st->seconds_per_day); /* days in seconds */
if (x2 == NULL) if (x2 == NULL)
goto Done; goto Done;
Py_SETREF(x1, NULL); Py_SETREF(x1, NULL);
@ -1862,7 +1868,7 @@ delta_to_microseconds(PyDateTime_Delta *self)
/* x1 = */ x2 = NULL; /* x1 = */ x2 = NULL;
/* x3 has days+seconds in seconds */ /* x3 has days+seconds in seconds */
x1 = PyNumber_Multiply(x3, us_per_second); /* us */ x1 = PyNumber_Multiply(x3, st->us_per_second); /* us */
if (x1 == NULL) if (x1 == NULL)
goto Done; goto Done;
Py_SETREF(x3, NULL); Py_SETREF(x3, NULL);
@ -1917,7 +1923,8 @@ microseconds_to_delta_ex(PyObject *pyus, PyTypeObject *type)
PyObject *num = NULL; PyObject *num = NULL;
PyObject *result = NULL; PyObject *result = NULL;
tuple = checked_divmod(pyus, us_per_second); datetime_state *st = STATIC_STATE();
tuple = checked_divmod(pyus, st->us_per_second);
if (tuple == NULL) { if (tuple == NULL) {
goto Done; goto Done;
} }
@ -1935,7 +1942,7 @@ microseconds_to_delta_ex(PyObject *pyus, PyTypeObject *type)
num = Py_NewRef(PyTuple_GET_ITEM(tuple, 0)); /* leftover seconds */ num = Py_NewRef(PyTuple_GET_ITEM(tuple, 0)); /* leftover seconds */
Py_DECREF(tuple); Py_DECREF(tuple);
tuple = checked_divmod(num, seconds_per_day); tuple = checked_divmod(num, st->seconds_per_day);
if (tuple == NULL) if (tuple == NULL)
goto Done; goto Done;
Py_DECREF(num); Py_DECREF(num);
@ -2535,28 +2542,29 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw)
y = accum("microseconds", x, us, _PyLong_GetOne(), &leftover_us); y = accum("microseconds", x, us, _PyLong_GetOne(), &leftover_us);
CLEANUP; CLEANUP;
} }
datetime_state *st = STATIC_STATE();
if (ms) { if (ms) {
y = accum("milliseconds", x, ms, us_per_ms, &leftover_us); y = accum("milliseconds", x, ms, st->us_per_ms, &leftover_us);
CLEANUP; CLEANUP;
} }
if (second) { if (second) {
y = accum("seconds", x, second, us_per_second, &leftover_us); y = accum("seconds", x, second, st->us_per_second, &leftover_us);
CLEANUP; CLEANUP;
} }
if (minute) { if (minute) {
y = accum("minutes", x, minute, us_per_minute, &leftover_us); y = accum("minutes", x, minute, st->us_per_minute, &leftover_us);
CLEANUP; CLEANUP;
} }
if (hour) { if (hour) {
y = accum("hours", x, hour, us_per_hour, &leftover_us); y = accum("hours", x, hour, st->us_per_hour, &leftover_us);
CLEANUP; CLEANUP;
} }
if (day) { if (day) {
y = accum("days", x, day, us_per_day, &leftover_us); y = accum("days", x, day, st->us_per_day, &leftover_us);
CLEANUP; CLEANUP;
} }
if (week) { if (week) {
y = accum("weeks", x, week, us_per_week, &leftover_us); y = accum("weeks", x, week, st->us_per_week, &leftover_us);
CLEANUP; CLEANUP;
} }
if (leftover_us) { if (leftover_us) {
@ -2711,7 +2719,8 @@ delta_total_seconds(PyObject *self, PyObject *Py_UNUSED(ignored))
if (total_microseconds == NULL) if (total_microseconds == NULL)
return NULL; return NULL;
total_seconds = PyNumber_TrueDivide(total_microseconds, us_per_second); datetime_state *st = STATIC_STATE();
total_seconds = PyNumber_TrueDivide(total_microseconds, st->us_per_second);
Py_DECREF(total_microseconds); Py_DECREF(total_microseconds);
return total_seconds; return total_seconds;
@ -3943,8 +3952,10 @@ timezone_repr(PyDateTime_TimeZone *self)
to use Py_TYPE(self)->tp_name here. */ to use Py_TYPE(self)->tp_name here. */
const char *type_name = Py_TYPE(self)->tp_name; const char *type_name = Py_TYPE(self)->tp_name;
if (((PyObject *)self) == PyDateTime_TimeZone_UTC) datetime_state *st = STATIC_STATE();
if (((PyObject *)self) == st->utc) {
return PyUnicode_FromFormat("%s.utc", type_name); return PyUnicode_FromFormat("%s.utc", type_name);
}
if (self->name == NULL) if (self->name == NULL)
return PyUnicode_FromFormat("%s(%R)", type_name, self->offset); return PyUnicode_FromFormat("%s(%R)", type_name, self->offset);
@ -3964,11 +3975,14 @@ timezone_str(PyDateTime_TimeZone *self)
if (self->name != NULL) { if (self->name != NULL) {
return Py_NewRef(self->name); return Py_NewRef(self->name);
} }
if ((PyObject *)self == PyDateTime_TimeZone_UTC || datetime_state *st = STATIC_STATE();
if ((PyObject *)self == st->utc ||
(GET_TD_DAYS(self->offset) == 0 && (GET_TD_DAYS(self->offset) == 0 &&
GET_TD_SECONDS(self->offset) == 0 && GET_TD_SECONDS(self->offset) == 0 &&
GET_TD_MICROSECONDS(self->offset) == 0)) GET_TD_MICROSECONDS(self->offset) == 0))
{
return PyUnicode_FromString("UTC"); return PyUnicode_FromString("UTC");
}
/* Offset is normalized, so it is negative if days < 0 */ /* Offset is normalized, so it is negative if days < 0 */
if (GET_TD_DAYS(self->offset) < 0) { if (GET_TD_DAYS(self->offset) < 0) {
sign = '-'; sign = '-';
@ -6134,7 +6148,8 @@ local_timezone(PyDateTime_DateTime *utc_time)
PyObject *one_second; PyObject *one_second;
PyObject *seconds; PyObject *seconds;
delta = datetime_subtract((PyObject *)utc_time, PyDateTime_Epoch); datetime_state *st = STATIC_STATE();
delta = datetime_subtract((PyObject *)utc_time, st->epoch);
if (delta == NULL) if (delta == NULL)
return NULL; return NULL;
one_second = new_delta(0, 1, 0, 0); one_second = new_delta(0, 1, 0, 0);
@ -6246,6 +6261,7 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
if (result == NULL) if (result == NULL)
return NULL; return NULL;
datetime_state *st = STATIC_STATE();
/* Make sure result is aware and UTC. */ /* Make sure result is aware and UTC. */
if (!HASTZINFO(result)) { if (!HASTZINFO(result)) {
temp = (PyObject *)result; temp = (PyObject *)result;
@ -6257,7 +6273,7 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
DATE_GET_MINUTE(result), DATE_GET_MINUTE(result),
DATE_GET_SECOND(result), DATE_GET_SECOND(result),
DATE_GET_MICROSECOND(result), DATE_GET_MICROSECOND(result),
PyDateTime_TimeZone_UTC, st->utc,
DATE_GET_FOLD(result), DATE_GET_FOLD(result),
Py_TYPE(result)); Py_TYPE(result));
Py_DECREF(temp); Py_DECREF(temp);
@ -6266,7 +6282,7 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
} }
else { else {
/* Result is already aware - just replace tzinfo. */ /* Result is already aware - just replace tzinfo. */
Py_SETREF(result->tzinfo, Py_NewRef(PyDateTime_TimeZone_UTC)); Py_SETREF(result->tzinfo, Py_NewRef(st->utc));
} }
/* Attach new tzinfo and let fromutc() do the rest. */ /* Attach new tzinfo and let fromutc() do the rest. */
@ -6370,8 +6386,9 @@ datetime_timestamp(PyDateTime_DateTime *self, PyObject *Py_UNUSED(ignored))
PyObject *result; PyObject *result;
if (HASTZINFO(self) && self->tzinfo != Py_None) { if (HASTZINFO(self) && self->tzinfo != Py_None) {
datetime_state *st = STATIC_STATE();
PyObject *delta; PyObject *delta;
delta = datetime_subtract((PyObject *)self, PyDateTime_Epoch); delta = datetime_subtract((PyObject *)self, st->epoch);
if (delta == NULL) if (delta == NULL)
return NULL; return NULL;
result = delta_total_seconds(delta, NULL); result = delta_total_seconds(delta, NULL);
@ -6692,10 +6709,11 @@ get_datetime_capi(void)
capi->Date_FromTimestamp = datetime_date_fromtimestamp_capi; capi->Date_FromTimestamp = datetime_date_fromtimestamp_capi;
capi->DateTime_FromDateAndTimeAndFold = new_datetime_ex2; capi->DateTime_FromDateAndTimeAndFold = new_datetime_ex2;
capi->Time_FromTimeAndFold = new_time_ex2; capi->Time_FromTimeAndFold = new_time_ex2;
// Make sure this function is called after PyDateTime_TimeZone_UTC has // Make sure this function is called after utc has
// been initialized. // been initialized.
assert(PyDateTime_TimeZone_UTC != NULL); datetime_state *st = STATIC_STATE();
capi->TimeZone_UTC = PyDateTime_TimeZone_UTC; // borrowed ref assert(st->utc != NULL);
capi->TimeZone_UTC = st->utc; // borrowed ref
return capi; return capi;
} }
@ -6706,6 +6724,85 @@ datetime_destructor(PyObject *op)
PyMem_Free(ptr); PyMem_Free(ptr);
} }
static int
datetime_clear(PyObject *module)
{
datetime_state *st = STATIC_STATE();
Py_CLEAR(st->us_per_ms);
Py_CLEAR(st->us_per_second);
Py_CLEAR(st->us_per_minute);
Py_CLEAR(st->us_per_hour);
Py_CLEAR(st->us_per_day);
Py_CLEAR(st->us_per_week);
Py_CLEAR(st->seconds_per_day);
Py_CLEAR(st->utc);
Py_CLEAR(st->epoch);
return 0;
}
static PyObject *
create_timezone_from_delta(int days, int sec, int ms, int normalize)
{
PyObject *delta = new_delta(days, sec, ms, normalize);
if (delta == NULL) {
return NULL;
}
PyObject *tz = create_timezone(delta, NULL);
Py_DECREF(delta);
return tz;
}
static int
init_state(datetime_state *st)
{
st->us_per_ms = PyLong_FromLong(1000);
if (st->us_per_ms == NULL) {
return -1;
}
st->us_per_second = PyLong_FromLong(1000000);
if (st->us_per_second == NULL) {
return -1;
}
st->us_per_minute = PyLong_FromLong(60000000);
if (st->us_per_minute == NULL) {
return -1;
}
st->seconds_per_day = PyLong_FromLong(24 * 3600);
if (st->seconds_per_day == NULL) {
return -1;
}
/* The rest are too big for 32-bit ints, but even
* us_per_week fits in 40 bits, so doubles should be exact.
*/
st->us_per_hour = PyLong_FromDouble(3600000000.0);
if (st->us_per_hour == NULL) {
return -1;
}
st->us_per_day = PyLong_FromDouble(86400000000.0);
if (st->us_per_day == NULL) {
return -1;
}
st->us_per_week = PyLong_FromDouble(604800000000.0);
if (st->us_per_week == NULL) {
return -1;
}
/* Init UTC timezone */
st->utc = create_timezone_from_delta(0, 0, 0, 0);
if (st->utc == NULL) {
return -1;
}
/* Init Unix epoch */
st->epoch = new_datetime(1970, 1, 1, 0, 0, 0, 0, st->utc, 0);
if (st->epoch == NULL) {
return -1;
}
return 0;
}
static int static int
_datetime_exec(PyObject *module) _datetime_exec(PyObject *module)
{ {
@ -6727,23 +6824,23 @@ _datetime_exec(PyObject *module)
for (size_t i = 0; i < Py_ARRAY_LENGTH(types); i++) { for (size_t i = 0; i < Py_ARRAY_LENGTH(types); i++) {
if (PyModule_AddType(module, types[i]) < 0) { if (PyModule_AddType(module, types[i]) < 0) {
return -1; goto error;
} }
} }
if (PyType_Ready(&PyDateTime_IsoCalendarDateType) < 0) { if (PyType_Ready(&PyDateTime_IsoCalendarDateType) < 0) {
return -1; goto error;
} }
#define DATETIME_ADD_MACRO(dict, c, value_expr) \ #define DATETIME_ADD_MACRO(dict, c, value_expr) \
do { \ do { \
PyObject *value = (value_expr); \ PyObject *value = (value_expr); \
if (value == NULL) { \ if (value == NULL) { \
return -1; \ goto error; \
} \ } \
if (PyDict_SetItemString(dict, c, value) < 0) { \ if (PyDict_SetItemString(dict, c, value) < 0) { \
Py_DECREF(value); \ Py_DECREF(value); \
return -1; \ goto error; \
} \ } \
Py_DECREF(value); \ Py_DECREF(value); \
} while(0) } while(0)
@ -6775,77 +6872,54 @@ _datetime_exec(PyObject *module)
999999, Py_None, 0)); 999999, Py_None, 0));
DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0)); DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0));
datetime_state *st = STATIC_STATE();
if (init_state(st) < 0) {
goto error;
}
/* timezone values */ /* timezone values */
d = PyDateTime_TimeZoneType.tp_dict; d = PyDateTime_TimeZoneType.tp_dict;
PyObject *delta = new_delta(0, 0, 0, 0); if (PyDict_SetItemString(d, "utc", st->utc) < 0) {
if (delta == NULL) { goto error;
return -1;
} }
PyObject *x = create_timezone(delta, NULL);
Py_DECREF(delta);
if (x == NULL) {
return -1;
}
if (PyDict_SetItemString(d, "utc", x) < 0) {
Py_DECREF(x);
return -1;
}
PyDateTime_TimeZone_UTC = x;
/* bpo-37642: These attributes are rounded to the nearest minute for backwards /* bpo-37642: These attributes are rounded to the nearest minute for backwards
* compatibility, even though the constructor will accept a wider range of * compatibility, even though the constructor will accept a wider range of
* values. This may change in the future.*/ * values. This may change in the future.*/
delta = new_delta(-1, 60, 0, 1); /* -23:59 */
if (delta == NULL) {
return -1;
}
x = create_timezone(delta, NULL); /* -23:59 */
Py_DECREF(delta); PyObject *min = create_timezone_from_delta(-1, 60, 0, 1);
DATETIME_ADD_MACRO(d, "min", x); DATETIME_ADD_MACRO(d, "min", min);
delta = new_delta(0, (23 * 60 + 59) * 60, 0, 0); /* +23:59 */ /* +23:59 */
if (delta == NULL) { PyObject *max = create_timezone_from_delta(0, (23 * 60 + 59) * 60, 0, 0);
return -1; DATETIME_ADD_MACRO(d, "max", max);
}
x = create_timezone(delta, NULL); /* Add module level attributes */
Py_DECREF(delta);
DATETIME_ADD_MACRO(d, "max", x);
/* Epoch */
PyDateTime_Epoch = new_datetime(1970, 1, 1, 0, 0, 0, 0,
PyDateTime_TimeZone_UTC, 0);
if (PyDateTime_Epoch == NULL) {
return -1;
}
/* module initialization */
if (PyModule_AddIntMacro(module, MINYEAR) < 0) { if (PyModule_AddIntMacro(module, MINYEAR) < 0) {
return -1; goto error;
} }
if (PyModule_AddIntMacro(module, MAXYEAR) < 0) { if (PyModule_AddIntMacro(module, MAXYEAR) < 0) {
return -1; goto error;
}
if (PyModule_AddObjectRef(module, "UTC", st->utc) < 0) {
goto error;
} }
/* At last, set up and add the encapsulated C API */
PyDateTime_CAPI *capi = get_datetime_capi(); PyDateTime_CAPI *capi = get_datetime_capi();
if (capi == NULL) { if (capi == NULL) {
return -1; goto error;
} }
x = PyCapsule_New(capi, PyDateTime_CAPSULE_NAME, datetime_destructor); PyObject *capsule = PyCapsule_New(capi, PyDateTime_CAPSULE_NAME,
if (x == NULL) { datetime_destructor);
if (capsule == NULL) {
PyMem_Free(capi); PyMem_Free(capi);
return -1; goto error;
} }
if (PyModule_Add(module, "datetime_CAPI", capsule) < 0) {
if (PyModule_Add(module, "datetime_CAPI", x) < 0) { PyMem_Free(capi);
return -1; goto error;
}
if (PyModule_AddObjectRef(module, "UTC", PyDateTime_TimeZone_UTC) < 0) {
return -1;
} }
/* A 4-year cycle has an extra leap day over what we'd get from /* A 4-year cycle has an extra leap day over what we'd get from
@ -6866,54 +6940,16 @@ _datetime_exec(PyObject *module)
static_assert(DI100Y == 25 * DI4Y - 1, "DI100Y"); static_assert(DI100Y == 25 * DI4Y - 1, "DI100Y");
assert(DI100Y == days_before_year(100+1)); assert(DI100Y == days_before_year(100+1));
us_per_ms = PyLong_FromLong(1000);
if (us_per_ms == NULL) {
goto error;
}
us_per_second = PyLong_FromLong(1000000);
if (us_per_second == NULL) {
goto error;
}
us_per_minute = PyLong_FromLong(60000000);
if (us_per_minute == NULL) {
goto error;
}
seconds_per_day = PyLong_FromLong(24 * 3600);
if (seconds_per_day == NULL) {
goto error;
}
/* The rest are too big for 32-bit ints, but even
* us_per_week fits in 40 bits, so doubles should be exact.
*/
us_per_hour = PyLong_FromDouble(3600000000.0);
if (us_per_hour == NULL) {
goto error;
}
us_per_day = PyLong_FromDouble(86400000000.0);
if (us_per_day == NULL) {
goto error;
}
us_per_week = PyLong_FromDouble(604800000000.0);
if (us_per_week == NULL) {
goto error;
}
return 0; return 0;
error: error:
Py_XDECREF(us_per_ms); datetime_clear(module);
Py_XDECREF(us_per_second);
Py_XDECREF(us_per_minute);
Py_XDECREF(us_per_hour);
Py_XDECREF(us_per_day);
Py_XDECREF(us_per_week);
Py_XDECREF(seconds_per_day);
return -1; return -1;
} }
#undef DATETIME_ADD_MACRO
static struct PyModuleDef datetimemodule = { static struct PyModuleDef datetimemodule = {
PyModuleDef_HEAD_INIT, .m_base = PyModuleDef_HEAD_INIT,
.m_name = "_datetime", .m_name = "_datetime",
.m_doc = "Fast implementation of the datetime type.", .m_doc = "Fast implementation of the datetime type.",
.m_size = -1, .m_size = -1,

View File

@ -418,20 +418,12 @@ Modules/_ctypes/_ctypes.c - _unpickle -
Modules/_ctypes/_ctypes.c PyCArrayType_from_ctype cache - Modules/_ctypes/_ctypes.c PyCArrayType_from_ctype cache -
Modules/_cursesmodule.c - ModDict - Modules/_cursesmodule.c - ModDict -
Modules/_datetimemodule.c datetime_strptime module - Modules/_datetimemodule.c datetime_strptime module -
Modules/_datetimemodule.c - PyDateTime_TimeZone_UTC -
Modules/_datetimemodule.c - PyDateTime_Epoch -
Modules/_datetimemodule.c - us_per_ms -
Modules/_datetimemodule.c - us_per_second -
Modules/_datetimemodule.c - us_per_minute -
Modules/_datetimemodule.c - us_per_hour -
Modules/_datetimemodule.c - us_per_day -
Modules/_datetimemodule.c - us_per_week -
Modules/_datetimemodule.c - seconds_per_day -
## state ## state
Modules/_ctypes/_ctypes.c - _ctypes_ptrtype_cache - Modules/_ctypes/_ctypes.c - _ctypes_ptrtype_cache -
Modules/_ctypes/_ctypes.c - global_state - Modules/_ctypes/_ctypes.c - global_state -
Modules/_ctypes/ctypes.h - global_state - Modules/_ctypes/ctypes.h - global_state -
Modules/_datetimemodule.c - _datetime_global_state -
Modules/_tkinter.c - tcl_lock - Modules/_tkinter.c - tcl_lock -
Modules/_tkinter.c - excInCmd - Modules/_tkinter.c - excInCmd -
Modules/_tkinter.c - valInCmd - Modules/_tkinter.c - valInCmd -

Can't render this file because it has a wrong number of fields in line 4.