Экспериментируя с магическими методами (в частности, __sizeof__
) на разных объектах Python, я наткнулся на следующее поведение:
Python 2.7
>>> False.__sizeof__()
24
>>> True.__sizeof__()
24
Python 3.x
>>> False.__sizeof__()
24
>>> True.__sizeof__()
28
Что изменилось в Python 3, из-за чего размер True
больше, чем размер False
?
Обратите внимание, что оценка потребления памяти с использованием sys.getsizeof
и __sizeof__
(в последнем случае не учитываются накладные расходы на сборку мусора) приведет к неверным результатам, если только кто-то действительно не понимает интерпретатор Python. PyPy считает, что использование любого из них является ошибка. Например, целые числа 5 <= i <= 256 являются одиночными в CPython - [1, 1]
и [1, 1, 1, 1]
отличаются только двумя дополнительными указателями по размеру. В вашем случае вам нужно будет выяснить, используют ли True
и 1
одну и ту же память для их стоимости.
Это потому, что bool
является подклассом int
как в Python 2, так и в 3.
>>> issubclass(bool, int)
True
Но реализация int
изменилась.
В Python 2 int
был 32- или 64-битным, в зависимости от системы, в отличие от long
произвольной длины.
В Python 3 int
имеет произвольную длину - long
Python 2 был переименован в int
, а исходный int
Python 2 был полностью удален.
В 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
- это одиночка, и поэтому только 4 байта теряются в общий в любой программе Python, а не 4 при каждом использовании True
.
Да, и длинные объекты просто не используют дополнительный указатель для представления 0
Спасибо за ответ. Я не знал, что bool является подклассом int, но теперь это имеет смысл!
Взгляните на код 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 }
Думаю, я просто не так быстро ответил должным образом, как другие :)
Ответ можно найти только на полпути. Он представлен как целое число .. да? И что?
@wim Я не вижу необходимости объяснять это дальше, так как ребята из вышеупомянутого уже отлично поработали. Я не хочу дублировать контент. См. Их ответы.
согласен, нехорошо дублировать контент в других ответах (в этих случаях обычно лучше просто удалить ответ самостоятельно). Просто пытался объяснить, почему он, возможно, был отклонен.
@wim Downvotes появился, когда я опубликовал первую версию ответа, вероятно, кому-то это не понравилось. Поскольку мой ответ неполный, и я не хочу дублировать лучшие ответы, следует ли мне удалить свой?
Я так думаю, но решать вам. Если вы подождете, пока он наберет +3, а затем удалите, вы можете заработать значок дисциплинированный ... :)
Я не видел для этого кода 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
+ байты для заголовков объектов должны завершать уравнение.
Собственно, теперь в Python 3 int
- это именно то, что было в Python 2 long
, это был действительно int
, который был «отброшен»
Внутренне - абсолютно верно, я про имена, но спасибо за пояснения
Действительно, в исходном коде CPython 3 это все еще longobject
На самом деле это деоптимизация ... пессимизация?
Наверное, нет. Возможно, я ошибаюсь, но эти накладные расходы не стоят столько, сколько два разных типа + бесшовное приведение во время выполнения. По правде говоря, вы редко используете такое огромное количество int в обычной программе Python, чтобы позаботиться об этой дополнительной памяти. Если вы это сделаете - вы должны использовать что-то вроде numpy для работы с ~ 0 накладными расходами и иметь чистый int32 / 64 - без заголовков, без перераспределения. Но время от времени переливается)
И True
, и False
имеют longobject
s в CPython:
struct _longobject _Py_FalseStruct = { PyVarObject_HEAD_INIT(&PyBool_Type, 0) { 0 } }; struct _longobject _Py_TrueStruct = { PyVarObject_HEAD_INIT(&PyBool_Type, 1) { 1 } };
Таким образом, вы можете сказать, что логическое значение является подклассом python-3.xint
, где True
принимает значение 1
, а False
принимает значение 0
. Таким образом, мы вызываем PyVarObject_HEAD_INIT
со ссылкой на type
в качестве параметра PyBool_Type
и с ob_size
в качестве значения 0
и 1
соответственно.
Теперь, после python-3.x, 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.
Таким образом, если значение равно нулю, добавляются цифры нет, тогда как для небольших целых чисел (значения меньше 230 в CPython) требуется одна цифра и так далее.
В python-2.x было два типа представления чисел: int
(с фиксированным размером), вы могли видеть это как «одну цифру», и long
, с несколькими цифрами. Поскольку bool
был подклассом int
, как True
, так и False
занимали одно и то же пространство.
Спасибо за хороший ответ! Если бы ТАК позволил мне принять несколько ответов, я бы принял и ваш! Думаю, следующим шагом к тому, чтобы стать лучшим программистом на Python, будет знакомство с важными частями исходного кода CPython ...
0
и1