builtins __eq__ __dict__ python python-3.x class dictionary python-internals

__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:

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:

  1. 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.

  2. 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?