Expérimenter avec des méthodes magiques (__sizeof__
_ en particulier) sur différents Python je suis tombé sur le comportement suivant:
Python 2.7
>>> False.__sizeof__()
24
>>> True.__sizeof__()
24
Python 3.x
>>> False.__sizeof__()
24
>>> True.__sizeof__()
28
Qu'est-ce qui a changé dans Python 3 qui rend la taille de True
supérieure à celle de False
?
C'est parce que bool
est une sous-classe de int
dans les deux Python 2 et 3.
>>> issubclass(bool, int)
True
Mais l'implémentation int
a changé.
Dans Python 2, int
était celui qui faisait 32 ou 64 bits, selon le système, par opposition à longueur arbitraire long
.
Dans Python 3, int
est une longueur arbitraire - le long
de Python 2 a été renommé en int
et l'original Python 2 int
totalement abandonné.
En Python 2 vous obtenez exactement le même comportement pour long objets 1L
et 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
Le long
/Python 3 int
est un objet de longueur variable, tout comme un tuple - lorsqu'il est alloué, suffisamment de mémoire est allouée pour contenir tous les chiffres binaires nécessaires à sa représentation. La longueur de la partie variable est stockée dans la tête de l'objet. 0
ne nécessite pas de chiffres binaires (sa longueur variable est 0), mais même 1
déborde et nécessite des chiffres supplémentaires.
C'est à dire. 0
est représenté par une chaîne binaire de longueur 0:
<>
et 1 est représenté par une chaîne binaire de 30 bits:
<000000000000000000000000000001>
La configuration par défaut de Python utilise bits dans un uint32_t
; so 2**30 - 1
tient toujours dans 28 octets sur x86-64, et 2**30
il en faudra 32;
2**30 - 1
sera présenté comme
<111111111111111111111111111111>
c'est-à-dire tous les 30 bits de valeur mis à 1; 2 ** 30 auront besoin de plus, et il aura une représentation interne
<000000000000000000000000000001000000000000000000000000000000>
Comme pour True
utiliser 28 octets au lieu de 24 - ne vous inquiétez pas. True
est un singleton et, par conséquent, seulement 4 octets sont perdus dans total dans tout Python, pas 4 pour chaque utilisation de True
.
True
et False
sont tous deux longobject
s en CPython:
struct _longobject _Py_FalseStruct = { PyVarObject_HEAD_INIT(&PyBool_Type, 0) { 0 } }; struct _longobject _Py_TrueStruct = { PyVarObject_HEAD_INIT(&PyBool_Type, 1) { 1 } };
Vous pouvez donc dire qu'un booléen est une sous-classe d'un python-3.xint
où True
prend pour valeur 1
et False
prend pour valeur 0
. Nous faisons donc un appel à PyVarObject_HEAD_INIT
avec comme paramètre type
une référence à PyBool_Type
et avec ob_size
comme valeur 0
et 1
respectivement.
Maintenant depuis python-3.x , il n'y a plus de long
: ils ont été fusionnés et l'objet int
sera, en fonction de la taille du nombre, prendre une valeur différente.
Si nous inspectons le code source du type longlobject
type , nous voyons:
/* 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]; };
Pour faire une histoire courte, un _longobject
peut être vu comme un tableau de "chiffres", mais vous devriez voir ici les chiffres non pas comme des chiffres décimaux, mais comme des groupes de bits qui peuvent ainsi être ajoutés, multipliés, etc.
Maintenant, comme spécifié dans le commentaire, il est dit que:
zero is represented by ob_size == 0.
Ainsi, dans le cas où la valeur est zéro, aucun chiffre n'est ajouté, alors que pour les petits entiers (valeurs inférieures à 230 dans CPython), il faut un chiffre, et ainsi de suite.
Dans python-2.x , il y avait deux types de représentations pour les nombres, int
s (avec une taille fixe), on pouvait voir cela comme "un chiffre" et long
s, avec plusieurs chiffres. Étant donné que bool
était une sous-classe de int
, True
et False
occupaient le même espace.
Jetez un coup d'œil au code cpython pour True
et False
En interne, il est représenté sous forme d'entier
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 }
Je n'ai pas vu le code CPython pour cela, mais je pense que cela a quelque chose à voir avec l'optimisation des entiers dans Python 3. Probablement, puisque long
a été abandonné, certaines optimisations ont été unifiées. int
en Python 3 est de taille arbitraire - identique à long
en Python 2. As bool
est stocké de la même manière que new int
, cela affecte les deux.
Partie intéressante:
>>> (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
+ octets pour les en-têtes d'objet devraient compléter l'équation.