__eq__ - ¿Por qué el__dict__ de las instancias es mucho más pequeño en Python 3?
__eq__ python (1)
En resumen :
Instance __dict__
''s se implementan de forma diferente que los diccionarios'' normales ''creados con dict
o {}
. Los diccionarios de una instancia comparten las claves y hash y mantienen una matriz separada para las partes que difieren: los valores. sys.getsizeof
solo cuenta esos valores al calcular el tamaño del dict de instancia.
Un poco más :
Los diccionarios en CPython son, a partir de Python 3.3, implementados en una de dos formas:
- Diccionario combinado : todos los valores del diccionario se almacenan junto con la clave y el hash para cada entrada. ( miembro
me_value
de la estructuraPyDictKeyEntry
). Hasta donde yo sé, este formulario se usa para diccionarios creados condict
,{}
y el espacio de nombres del módulo. - Tabla dividida : los valores se almacenan por separado en una matriz, mientras que las claves y hashes se comparten ( valores almacenados en
ma_values
dePyDictObject
)
Los diccionarios de instancia siempre se implementan en una tabla dividida (un Diccionario de Key-Sharing) que permite a las instancias de una clase determinada compartir las claves (y hash) para su __dict__
y solo difieren en los valores correspondientes.
Todo esto se describe en PEP 412 - Key-Sharing Dictionary . La implementación del diccionario dividido llegó a Python 3.3
, por lo que las versiones anteriores de la familia 3
y Python 2.x
no tienen esta implementación.
La implementación de __sizeof__
para diccionarios tiene en cuenta este hecho y solo considera el tamaño que corresponde a la matriz de valores al calcular el tamaño de un diccionario dividido.
Por suerte, se explica por sí mismo:
Py_ssize_t size, res;
size = DK_SIZE(mp->ma_keys);
res = _PyObject_SIZE(Py_TYPE(mp));
if (mp->ma_values) /*Add the values to the result*/
res += size * sizeof(PyObject*);
/* If the dictionary is split, the keys portion is accounted-for
in the type object. */
if (mp->ma_keys->dk_refcnt == 1) /* Add keys/hashes size to res */
res += sizeof(PyDictKeysObject) + (size-1) * sizeof(PyDictKeyEntry);
return res;
Hasta donde yo sé, los diccionarios de tabla dividida se crean solo para el espacio de nombres de las instancias , usando dict()
o {}
(como también se describe en el PEP) siempre da como resultado un diccionario combinado que no tiene estos beneficios.
Como un aparte, ya que es divertido, siempre podemos romper esta optimización. Hay dos formas actuales que he encontrado actualmente, una manera tonta o por un escenario más sensato:
Haciendo el tonto:
>>> f = Foo(20, 30) >>> getsizeof(vars(f)) 96 >>> vars(f).update({1:1}) # add a non-string key >>> getsizeof(vars(f)) 288
Las tablas divididas solo admiten claves de cadena, al agregar una clave que no sea de cadena (lo que realmente tiene sentido) rompe esta regla y CPython convierte la tabla dividida en una combinada que pierde todas las ganancias de memoria.
Un escenario que podría suceder:
>>> f1, f2 = Foo(20, 30), Foo(30, 40) >>> for i, j in enumerate([f1, f2]): ... setattr(j, ''i''+str(i), i) ... print(getsizeof(vars(j))) 96 288
Cuando se insertan diferentes claves en las instancias de una clase, eventualmente se combinará la tabla dividida. Esto no se aplica solo a las instancias ya creadas; todas las instancias consecuentes creadas a partir de la clase tendrán un diccionario combinado en lugar de uno dividido.
# after running previous snippet >>> getsizeof(vars(Foo(100, 200))) 288
por supuesto, no hay una buena razón, aparte de la diversión, para hacer esto a propósito.
Si alguien está vagando, la implementación del diccionario de Python 3.6 no cambia este hecho. Las dos formas de diccionarios antes mencionadas, cuando todavía están disponibles, se compactan aún más (la implementación de dict.__sizeof__
también cambió, por lo que algunas diferencias deberían aparecer en los valores devueltos por getsizeof
).
En Python, los diccionarios creados para las instancias de una clase son pequeños en comparación con los diccionarios creados que contienen los mismos atributos de esa clase:
import sys
class Foo(object):
def __init__(self, a, b):
self.a = a
self.b = b
f = Foo(20, 30)
Al usar Python 3.5.2, las siguientes llamadas a getsizeof
producen:
>>> sys.getsizeof(vars(f)) # vars gets obj.__dict__
96
>>> sys.getsizeof(dict(vars(f))
288
288 - 96 = 192
bytes guardados!
Usando Python 2.7.12, sin embargo, por otro lado, vuelven las mismas llamadas:
>>> sys.getsizeof(vars(f))
280
>>> sys.getsizeof(dict(vars(f)))
280
0
bytes guardados.
En ambos casos, los diccionarios obviamente tienen exactamente los mismos contenidos :
>>> vars(f) == dict(vars(f))
True
entonces este no es un factor. Además, esto también se aplica a Python 3 solamente.
Entonces, ¿qué está pasando aquí? ¿Por qué el tamaño del __dict__
de una instancia es tan pequeño en Python 3?