Python 如何在内部存储日期时间?
How does Python store datetime internally?
我发现 _datetimemodule.c
似乎是正确的文件,但我需要一些帮助,因为 C 不是我的强项。
>>> import datetime
>>> import sys
>>> d = datetime.datetime.now()
>>> sys.getsizeof(d)
48
>>> d = datetime.datetime(2018, 12, 31, 23, 59, 59, 123)
>>> sys.getsizeof(d)
48
所以一个不识别时区的日期时间对象需要 48 字节。看PyDateTime_DateTimeType
,好像是一个PyDateTime_DateType
,一个PyDateTime_TimeType
。也许还有 _PyDateTime_BaseTime
?
看代码,感觉YYYY-mm-dd HH:MM:ss
中每个字段都存储了一个组件,意思是:
- 年份:例如int(例如
int16_t
将是 16 位)
- 月份:例如
int8_t
- 天:例如
int8_t
- 小时:例如
int8_t
- 分钟:例如
int8_t
- 第二个:例如
int8_t
- 微秒:例如
uint16_t
但这将是 2*16 + 5 * 8 = 72 位 = 9 字节,而不是 Python 告诉我的 48 字节。
我对 datetime 内部结构的假设哪里错了?我如何在代码中看到这个?
(我想这可能在 Python 实现之间有所不同 - 如果是这样,请关注 cPython)
您遗漏了图片的关键部分:实际的日期时间结构定义,位于 Include/datetime.h
中。里面也有重要的评论。以下是一些重要摘录:
/* Fields are packed into successive bytes, each viewed as unsigned and
* big-endian, unless otherwise noted:
*
* byte offset
* 0 year 2 bytes, 1-9999
* 2 month 1 byte, 1-12
* 3 day 1 byte, 1-31
* 4 hour 1 byte, 0-23
* 5 minute 1 byte, 0-59
* 6 second 1 byte, 0-59
* 7 usecond 3 bytes, 0-999999
* 10
*/
...
/* # of bytes for year, month, day, hour, minute, second, and usecond. */
#define _PyDateTime_DATETIME_DATASIZE 10
...
/* The datetime and time types have hashcodes, and an optional tzinfo member,
* present if and only if hastzinfo is true.
*/
#define _PyTZINFO_HEAD \
PyObject_HEAD \
Py_hash_t hashcode; \
char hastzinfo; /* boolean flag */
...
/* All datetime objects are of PyDateTime_DateTimeType, but that can be
* allocated in two ways too, just like for time objects above. In addition,
* the plain date type is a base class for datetime, so it must also have
* a hastzinfo member (although it's unused there).
*/
...
#define _PyDateTime_DATETIMEHEAD \
_PyTZINFO_HEAD \
unsigned char data[_PyDateTime_DATETIME_DATASIZE];
typedef struct
{
_PyDateTime_DATETIMEHEAD
} _PyDateTime_BaseDateTime; /* hastzinfo false */
typedef struct
{
_PyDateTime_DATETIMEHEAD
unsigned char fold;
PyObject *tzinfo;
} PyDateTime_DateTime; /* hastzinfo true */
此外,请注意 Modules/_datetimemodule.c
中的以下 lines:
static PyTypeObject PyDateTime_DateTimeType = {
PyVarObject_HEAD_INIT(NULL, 0)
"datetime.datetime", /* tp_name */
sizeof(PyDateTime_DateTime), /* tp_basicsize */
那 tp_basicsize
行表示 sizeof(PyDateTime_DateTime)
,而不是 sizeof(_PyDateTime_BaseDateTime)
,并且该类型没有实现任何特殊的 __sizeof__
处理。这意味着 datetime.datetime
类型将其实例大小报告为时区感知日期时间的大小,即使对于不感知的实例也是如此。
您看到的 48 字节计数细分如下:
- 8 字节引用计数
- 8 字节类型指针
- 8 字节缓存哈希
- 1 字节“hastzinfo”标志
- 10 字节手动打包
unsigned char[10]
包含日期时间数据
- 1 字节“折叠”标志 (DST-related)
- 4 字节填充,对齐 tzinfo 指针
- 8 字节 tzinfo 指针
即使您不知道的实例的实际内存布局没有折叠标志或 tzinfo 指针,也是如此。
当然,这就是所有实施细节。它可能在不同的 Python 实现、不同的 CPython 版本、32 位 CPython 构建或 CPython 调试构建上有所不同(有当 CPython 编译时定义了 Py_TRACE_REFS 时 PyObject_HEAD 中的额外内容)。
我发现 _datetimemodule.c
似乎是正确的文件,但我需要一些帮助,因为 C 不是我的强项。
>>> import datetime
>>> import sys
>>> d = datetime.datetime.now()
>>> sys.getsizeof(d)
48
>>> d = datetime.datetime(2018, 12, 31, 23, 59, 59, 123)
>>> sys.getsizeof(d)
48
所以一个不识别时区的日期时间对象需要 48 字节。看PyDateTime_DateTimeType
,好像是一个PyDateTime_DateType
,一个PyDateTime_TimeType
。也许还有 _PyDateTime_BaseTime
?
看代码,感觉YYYY-mm-dd HH:MM:ss
中每个字段都存储了一个组件,意思是:
- 年份:例如int(例如
int16_t
将是 16 位) - 月份:例如
int8_t
- 天:例如
int8_t
- 小时:例如
int8_t
- 分钟:例如
int8_t
- 第二个:例如
int8_t
- 微秒:例如
uint16_t
但这将是 2*16 + 5 * 8 = 72 位 = 9 字节,而不是 Python 告诉我的 48 字节。
我对 datetime 内部结构的假设哪里错了?我如何在代码中看到这个?
(我想这可能在 Python 实现之间有所不同 - 如果是这样,请关注 cPython)
您遗漏了图片的关键部分:实际的日期时间结构定义,位于 Include/datetime.h
中。里面也有重要的评论。以下是一些重要摘录:
/* Fields are packed into successive bytes, each viewed as unsigned and
* big-endian, unless otherwise noted:
*
* byte offset
* 0 year 2 bytes, 1-9999
* 2 month 1 byte, 1-12
* 3 day 1 byte, 1-31
* 4 hour 1 byte, 0-23
* 5 minute 1 byte, 0-59
* 6 second 1 byte, 0-59
* 7 usecond 3 bytes, 0-999999
* 10
*/
...
/* # of bytes for year, month, day, hour, minute, second, and usecond. */
#define _PyDateTime_DATETIME_DATASIZE 10
...
/* The datetime and time types have hashcodes, and an optional tzinfo member,
* present if and only if hastzinfo is true.
*/
#define _PyTZINFO_HEAD \
PyObject_HEAD \
Py_hash_t hashcode; \
char hastzinfo; /* boolean flag */
...
/* All datetime objects are of PyDateTime_DateTimeType, but that can be
* allocated in two ways too, just like for time objects above. In addition,
* the plain date type is a base class for datetime, so it must also have
* a hastzinfo member (although it's unused there).
*/
...
#define _PyDateTime_DATETIMEHEAD \
_PyTZINFO_HEAD \
unsigned char data[_PyDateTime_DATETIME_DATASIZE];
typedef struct
{
_PyDateTime_DATETIMEHEAD
} _PyDateTime_BaseDateTime; /* hastzinfo false */
typedef struct
{
_PyDateTime_DATETIMEHEAD
unsigned char fold;
PyObject *tzinfo;
} PyDateTime_DateTime; /* hastzinfo true */
此外,请注意 Modules/_datetimemodule.c
中的以下 lines:
static PyTypeObject PyDateTime_DateTimeType = {
PyVarObject_HEAD_INIT(NULL, 0)
"datetime.datetime", /* tp_name */
sizeof(PyDateTime_DateTime), /* tp_basicsize */
那 tp_basicsize
行表示 sizeof(PyDateTime_DateTime)
,而不是 sizeof(_PyDateTime_BaseDateTime)
,并且该类型没有实现任何特殊的 __sizeof__
处理。这意味着 datetime.datetime
类型将其实例大小报告为时区感知日期时间的大小,即使对于不感知的实例也是如此。
您看到的 48 字节计数细分如下:
- 8 字节引用计数
- 8 字节类型指针
- 8 字节缓存哈希
- 1 字节“hastzinfo”标志
- 10 字节手动打包
unsigned char[10]
包含日期时间数据 - 1 字节“折叠”标志 (DST-related)
- 4 字节填充,对齐 tzinfo 指针
- 8 字节 tzinfo 指针
即使您不知道的实例的实际内存布局没有折叠标志或 tzinfo 指针,也是如此。
当然,这就是所有实施细节。它可能在不同的 Python 实现、不同的 CPython 版本、32 位 CPython 构建或 CPython 调试构建上有所不同(有当 CPython 编译时定义了 Py_TRACE_REFS 时 PyObject_HEAD 中的额外内容)。