Tamaño de objeto diferente de Verdadero y Falso en Python 3
python-3.x python-2.7 (4)
Eche un vistazo al
código cpython
para
True
y
False
Internamente se representa como un entero
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 }
Experimentando con métodos mágicos (
__sizeof__
en particular) en diferentes objetos de Python me topé con el siguiente comportamiento:
Python 2.7
>>> False.__sizeof__()
24
>>> True.__sizeof__()
24
Python 3.x
>>> False.__sizeof__()
24
>>> True.__sizeof__()
28
¿Qué cambió en Python 3 que hace que el tamaño de
True
mayor que el tamaño de
False
?
No he visto el código CPython para esto, pero creo que esto tiene algo que ver con la optimización de los enteros en Python 3. Probablemente, mientras se eliminó, algunas optimizaciones se unificaron.
int
en Python 3 es int de tamaño arbitrario, el mismo
long
estuvo en Python 2. Como
bool
almacena de la misma manera que new
int
, afecta a ambos.
Parte interesante:
>>> (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
+ bytes para encabezados de objetos deben completar la ecuación.
Se debe a que
bool
es una subclase de
int
en Python 2 y 3.
>>> issubclass(bool, int)
True
Pero la implementación de
int
ha cambiado.
En Python 2,
int
era el que tenía 32 o 64 bits, dependiendo del sistema, en contraposición a la longitud arbitraria.
En Python 3,
int
es de longitud arbitraria: la parte
long
de Python 2 se cambió de nombre a
int
y la
int
original de Python 2 se eliminó por completo.
En Python 2, obtienes exactamente el mismo comportamiento para
los
objetos
largos
1L
y
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
es un objeto de longitud variable, como una tupla: cuando se asigna, se asigna suficiente memoria para contener todos los dígitos binarios necesarios para representarlo.
La longitud de la parte variable se almacena en la cabeza del objeto.
0
no requiere dígitos binarios (su longitud variable es 0), pero incluso
1
derrama, y requiere dígitos adicionales.
Ie
0
se representa como una cadena binaria de longitud 0:
<>
y 1 se representa como una cadena binaria de 30 bits:
<000000000000000000000000000001>
La configuración predeterminada en Python usa
30 bits en un
uint32_t
;
so 2**30 - 1
aún encaja en 28 bytes en x86-64, y
2**30
requerirá 32;
2**30 - 1
será presentado como
<111111111111111111111111111111>
es decir, todos los 30 bits de valor establecidos en 1; 2 ** 30 necesitarán más, y tendrá representación interna.
<000000000000000000000000000001000000000000000000000000000000>
En cuanto a
True
utiliza
28
bytes en lugar de
24
; no debes preocuparte.
True
es un
singleton
y, por lo tanto, solo se pierden 4 bytes en
total
en cualquier programa de Python, no 4 por cada uso de
True
.
Tanto el
True
como el
False
son
longobject
en CPython:
struct _longobject _Py_FalseStruct = { PyVarObject_HEAD_INIT(&PyBool_Type, 0) { 0 } }; struct _longobject _Py_TrueStruct = { PyVarObject_HEAD_INIT(&PyBool_Type, 1) { 1 } };
Por lo tanto, puede decir que un valor booleano es una subclase de un
int
python-3.x
donde
True
toma como valor
1
, y
False
toma como valor
0
.
Por lo tanto, hacemos una llamada a
PyVarObject_HEAD_INIT
con como parámetro de
type
una referencia a
PyBool_Type
y con
ob_size
como valor
0
y
1
respectivamente.
Ahora, desde
python-3.x
, ya no hay más: se han fusionado, y el objeto
int
, dependiendo del tamaño del número, tomará un valor diferente.
Si inspeccionamos el código fuente del
tipo
longlobject
, vemos:
/* 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]; };
Para
_longobject
, un objeto
_longobject
puede verse como una matriz de "dígitos", pero aquí debería ver los dígitos no como dígitos decimales, sino como grupos de bits que, por lo tanto, se pueden agregar, multiplicar, etc.
Ahora como se especifica en el comentario, dice que:
zero is represented by ob_size == 0.
Entonces, en caso de que el valor sea cero, no se agregan dígitos, mientras que para los enteros pequeños (valores inferiores a 2 30 en CPython), toma un dígito, y así sucesivamente.
En
python-2.x
, había dos tipos de representaciones para números,
int
s (con un tamaño fijo), se podía ver como "un dígito" y
long
s, con varios dígitos.
Dado que un
bool
era una subclase de
int
,
True
y
False
ocupaban el mismo espacio.