Различный размер объекта True и False в Python 3 - PullRequest
0 голосов
/ 26 октября 2018

Экспериментируя с магическими методами (в частности __sizeof__) на различных объектах Python, я наткнулся на следующее поведение:

Python 2.7

>>> False.__sizeof__()
24
>>> True.__sizeof__()
24

Python 3.x

>>> False.__sizeof__()
24
>>> True.__sizeof__()
28

Что изменилось в Python 3, из-за чего размер True превышает размер False?

Ответы [ 4 ]

0 голосов
/ 26 октября 2018

И 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 }
};

Таким образом, вы можете сказать, что логическое значение является подклассом int, где True принимает в качестве значения 1, а False принимает в качестве значения 0 , Таким образом, мы вызываем PyVarObject_HEAD_INIT с параметром type со ссылкой на PyBool_Type и ob_size в качестве значения 0 и 1 соответственно.

Теперь, начиная с , long больше нет: они были объединены, и объект int в зависимости от размера числа примет другое значение .

Если мы проверим исходный код типа 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's sign bit.
*/

struct _longobject {
    PyObject_VAR_HEAD
    digit ob_digit[1];
};

Короче говоря, _longobject можно рассматривать как массив "цифр", но здесь вы должны видеть цифры не как десятичные цифры, а как группы битов, которые, таким образом, могут быть добавлены, умножены и т. Д. .

Теперь, как указано в комментарии, говорится, что:

   zero is represented by ob_size == 0.

Таким образом, в случае, если значение равно нулю, нет цифр добавляется, тогда как для маленьких целых чисел (значения менее 2 30 в CPython) требуется одна цифра и т. Д. .

В было два типа представлений для чисел: int с (с фиксированным размером), вы могли видеть это как «одну цифру», и long s, с несколькими цифрами. Поскольку bool был подклассом int, оба True и False занимали одно и то же пространство.

0 голосов
/ 26 октября 2018

Взгляните на код cpython для True и False

Внутренне это представляется как целое число

PyTypeObject PyBool_Type = {
        PyVarObject_HEAD_INIT(&PyType_Type, 0)
        "bool",
        <b>sizeof(struct _longobject)</b>,
        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 */
        <b>&PyLong_Type,                               /* tp_base */</b>
        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 }
0 голосов
/ 26 октября 2018

Я не видел код CPython для этого, но я полагаю, что это как-то связано с оптимизацией целых чисел в Python 3. Вероятно, поскольку long был отброшен, некоторые оптимизации были объединены.int в Python 3 имеет тип int произвольного размера - так же, как long было в Python 2. Так как bool хранит так же, как новый 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

+ байт для заголовков объекта должны завершить уравнение.

0 голосов
/ 26 октября 2018

Это потому, что 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 вы получаете точно такой жеповедение для long объектов 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.

...