Различный размер объекта True и False в Python 3
Экспериментируя с магическими методами (в частности, __sizeof__
) на различных объектах Python, я наткнулся на следующее поведение:
Python 2.7
>>> False.__sizeof__()
24
>>> True.__sizeof__()
24
Python 3.x
>>> False.__sizeof__()
24
>>> True.__sizeof__()
28
Что изменилось в Python 3, что делает размер True
больше размера False
?
Ответы
Ответ 1
Это потому, что bool
является подклассом int
в Python 2 и 3.
>>> issubclass(bool, int)
True
Но реализация int
изменилась.
В Python 2, int
был тот, который был 32 или 64 бит, в зависимости от системы, в отличие от произвольной длины long
.
В Python 3 int
является произвольной длины - long
из Python 2 был переименован в int
и оригинальный Python 2 int
упал в целом.
В Python 2 вы получаете точно такое же поведение для длинных объектов 1L
и 0L
:
Python 2.7.15rc1 (default, Apr 15 2018, 21:51:34)
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getsizeof(1L)
28
>>> sys.getsizeof(0L)
24
long
/Python 3 int
- это объект переменной длины, как и кортеж - когда он выделяется, выделяется достаточно памяти для хранения всех двоичных цифр, необходимых для его представления. Длина переменной части хранится в заголовке объекта. 0
требует двоичных цифр (его переменная длина равна 0), но даже 1
выходит за пределы и требует дополнительных цифр.
Т.е. 0
представляется в виде двоичной строки длиной 0:
<>
и 1 представлен в виде 30-битной двоичной строки:
<000000000000000000000000000001>
Конфигурация по умолчанию в Python использует 30 бит в uint32_t
; so 2**30 - 1
прежнему умещается в 28 байт на x86-64, а 2**30
потребует 32;
2**30 - 1
будет представлен как
<111111111111111111111111111111>
т.е. все 30 битов значения установлены в 1; 2 ** 30 потребуется больше, и у него будет внутреннее представление
<000000000000000000000000000001000000000000000000000000000000>
Что касается True
использующего 28 байтов вместо 24 - вам не нужно беспокоиться. True
- одноэлементный файл, поэтому в любой программе на Python потеряно всего 4 байта, а не 4 при каждом использовании True
.
Ответ 2
True
и False
- это longobject
в CPython:
struct _longobject _Py_FalseStruct = {
PyVarObject_HEAD_INIT(&PyBool_Type, 0)
{ 0 }
};
struct _longobject _Py_TrueStruct = {
PyVarObject_HEAD_INIT(&PyBool_Type, 1)
{ 1 }
};
Таким образом, вы можете сказать, что Boolean - это подкласс int
python-3.x, где True
принимает значение 1
, а False
принимает значение 0
. Таким образом, мы делаем вызов PyVarObject_HEAD_INIT
с параметром type
со ссылкой на PyBool_Type
и с ob_size
как значения 0
и 1
соответственно.
Теперь, начиная с python-3.x, больше нет long
: они были объединены, и объект int
, в зависимости от размера числа, примет другое значение.
Если мы longlobject
исходный код типа longlobject
, мы увидим:
/* Long integer representation.
The absolute value of a number is equal to
SUM(for i=0 through abs(ob_size)-1) ob_digit[i] * 2**(SHIFT*i)
Negative numbers are represented with ob_size < 0;
zero is represented by ob_size == 0.
In a normalized number, ob_digit[abs(ob_size)-1] (the most significant
digit) is never zero. Also, in all cases, for all valid i,
0 <= ob_digit[i] <= MASK.
The allocation function takes care of allocating extra memory
so that ob_digit[0] ... ob_digit[abs(ob_size)-1] are actually available.
CAUTION: Generic code manipulating subtypes of PyVarObject has to
aware that ints abuse ob_size sign bit.
*/
struct _longobject {
PyObject_VAR_HEAD
digit ob_digit[1];
};
Короче говоря, _longobject
можно рассматривать как массив "цифр", но здесь вы должны видеть цифры не как десятичные цифры, а как группы битов, которые, таким образом, могут быть добавлены, умножены и т.д.
Теперь, как указано в комментарии, это говорит о том, что:
zero is represented by ob_size == 0.
Таким образом, если значение равно нулю, никакие цифры не добавляются, тогда как для маленьких целых чисел (значения менее 2 30 в CPython) требуется одна цифра и так далее.
В python-2.x было два типа представлений для чисел, int
(с фиксированным размером), вы могли видеть это как "одна цифра" и long
s, с несколькими цифрами. Поскольку bool
является подклассом int
, оба значения True
и False
занимают одно и то же пространство.
Ответ 3
Посмотрите на код cpython для True
и False
Внутренне это представляется как целое число
PyTypeObject PyBool_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"bool",
sizeof(struct _longobject),
0,
0, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
bool_repr, /* tp_repr */
&bool_as_number, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
bool_repr, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
bool_doc, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
&PyLong_Type, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
bool_new, /* tp_new */
};
/* The objects representing bool values False and True */
struct _longobject _Py_FalseStruct = {
PyVarObject_HEAD_INIT(&PyBool_Type, 0)
{ 0 }
};
struct _longobject _Py_TrueStruct = {
PyVarObject_HEAD_INIT(&PyBool_Type, 1)
{ 1 }
Ответ 4
Я не видел код CPython для этого, но я полагаю, что это как-то связано с оптимизацией целых чисел в Python 3. Вероятно, так как long
отбрасывали, некоторые оптимизации были объединены. int
в Python 3 имеет тип int произвольного размера - так же, как long
был в Python 2. Поскольку bool
хранит так же, как new int
, он влияет на оба.
Интересная часть:
>>> (0).__sizeof__()
24
>>> (1).__sizeof__() # Here one more "block" is allocated
28
>>> (2**30-1).__sizeof__() # This is the maximum integer size fitting into 28
28
+ байты для заголовков объекта должны завершить уравнение.