2010-10-31 8 views
6

Decir que tengo una clase A:¿En qué estructura está almacenado un objeto Python en la memoria?

class A(object): 
    def __init__(self, x): 
     self.x = x 

    def __str__(self): 
     return self.x 

Y uso sys.getsizeof para ver cuántos ejemplo bytes de A toma:

>>> sys.getsizeof(A(1)) 
64 
>>> sys.getsizeof(A('a')) 
64 
>>> sys.getsizeof(A('aaa')) 
64 

Como se ilustra en el experimento anterior, el tamaño de un objeto A es lo mismo sin importar lo que self.x es.

Así que me pregunto cómo Python almacena un objeto internamente?

+0

Seguramente esto diferirá en las implementaciones de python. De cual estas hablando? –

Respuesta

22

Depende de qué tipo de objeto, y también qué aplicación Python :-)

En CPython, que es lo más personas usan cuando utilizan python, todos los objetos de Python están representados por una estructura C, PyObject. Todo lo que 'almacena un objeto' realmente almacena un PyObject *. La estructura PyObject contiene la información mínima: el tipo del objeto (un puntero a otro PyObject) y su recuento de referencia (un ssize_t -contexto totalizado). Los tipos definidos en C amplían esta estructura con información adicional que necesitan almacenar en el objeto mismo, y a veces asignar datos adicionales por separado.

Por ejemplo, las tuplas (implementados como PyTupleObject "extender" un struct PyObject) almacenar su longitud y las PyObject punteros que contienen dentro de la propia (struct la struct contiene una matriz de 1 de longitud en la definición, pero los asigna implementación un bloque de memoria del tamaño correcto para contener la estructura PyTupleObject más exactamente tantos elementos como debería contener la tupla.) Del mismo modo, las cadenas (PyStringObject) almacenan su longitud, su valor en hash en caché, algo de caché de cadenas ("interning") teneduría de libros, y el char * real de sus datos. Las tuplas y las cuerdas son, por lo tanto, bloques únicos de memoria.

Por otro lado, las listas (PyListObject) guardar su longitud, un PyObject ** por sus datos y otro ssize_t hacer un seguimiento de la cantidad de espacio que se asignan para los datos. Debido a que Python almacena punteros PyObject en todas partes, no se puede hacer crecer una estructura PyObject una vez que se haya asignado, ya que puede ser necesario mover la estructura, lo que significa encontrar todos los punteros y actualizarlos. Debido a que una lista puede necesitar crecer, tiene que asignar los datos por separado de la estructura de PyObject. Las tuplas y las cadenas no pueden crecer, por lo que no necesitan esto. Los dicts (PyDictObject) funcionan de la misma manera, aunque almacenan la clave, el valor y el hashvalue en caché de la clave, en lugar de solo los elementos. Dict también tiene una sobrecarga adicional para acomodar pequeños dicts y funciones especializadas de búsqueda.

Pero estos son todos los tipos en C, y normalmente se puede ver la cantidad de memoria que usarían con solo mirar la fuente C. Las instancias de clases definidas en Python en lugar de C no son tan fáciles.El caso más simple, instancias de clases clásicas, no es tan difícil: es un PyObject que almacena un PyObject * en su clase (que ya no es lo mismo que el tipo almacenado en la estructura PyObject), un PyObject * en su atributo __dict__ (que contiene todos los demás atributos de instancia) y PyObject * en su lista de referencia débil (que es utilizada por el módulo weakref, y solo se inicializa si es necesario). La instancia __dict__ es generalmente única para la instancia, por lo que al calcular el "tamaño de memoria" de dicha instancia Por lo general, también desea contar el tamaño del atributo dict. ¡Pero no tiene que ser específico para la instancia! __dict__ se puede asignar a muy bien.

Las clases de nuevo estilo complican las maneras. A diferencia de las clases clásicas, las instancias de clases de nuevo estilo no son tipos de C separados, por lo que no necesitan almacenar la clase del objeto por separado. Tienen espacio para la referencia __dict__ y de lista débil, pero a diferencia de las instancias clásicas no requieren el atributo __dict__ para atributos arbitrarios. si la clase (y todas sus clases base) usan __slots__ para definir un conjunto estricto de atributos, y ninguno de esos atributos se llama __dict__, la instancia no permite atributos arbitrarios y no se asigna ningún dict. Por otro lado, los atributos definidos por __slots__ deben almacenarse en algún lugar. Esto se hace almacenando los punteros PyObject para los valores de esos atributos directamente en la estructura PyObject, al igual que con los tipos escritos en C. Cada entrada en __slots__ ocupará así un PyObject *, independientemente de si el atributo está establecido o no. .

Dicho todo esto, el problema sigue siendo que, como todo en Python es un objeto y todo lo que contiene un objeto solo contiene una referencia, a veces es muy difícil trazar la línea entre los objetos. Dos objetos pueden referirse al mismo bit de datos. Pueden contener las dos únicas referencias a esa información. Deshacerse de ambos objetos también elimina los datos. ¿Ambos poseen los datos? ¿Solo uno de ellos, pero si es así, cuál? ¿O dirías que poseen la mitad de los datos, aunque deshacerse de un objeto no libere la mitad de los datos? Weakrefs puede complicar aún más esto: dos objetos pueden referirse a los mismos datos, pero eliminar uno de ellos puede hacer que el otro objeto también elimine su referencia a esos datos, haciendo que los datos se limpien después de todo .

Afortunadamente la caso común es bastante fácil de entender. Hay depuradores de memoria para Python que hacen un trabajo razonable en el seguimiento de estas cosas, como heapy. Y siempre que su clase (y sus clases de base) sean razonablemente simples, puede adivinar cuánta memoria ocuparía, especialmente en grandes cantidades. Si realmente quiere saber el tamaño exacto de sus estructuras de datos, consulte la fuente CPython; la mayoría de los tipos incorporados son estructuras simples descritas en Include/<type>object.h e implementadas en Objects/<type>object.c. La estructura de PyObject se describe en Include/object.h. Solo ten en cuenta: son punteros todo el camino hacia abajo; esos ocupan espacio también.

+0

Muchas gracias. De hecho, estoy haciendo esta pregunta porque quiero saber qué está almacenado en memcached cuando invoco 'cache.set (key, obj)', ¿es algo así como un objeto escabechado? – satoru

+4

¡Oh, bueno! Esa es una pregunta completamente diferente. Según recuerdo (y un vistazo rápido a la fuente confirma), el módulo 'Memcache' almacena versiones en escabeche del objeto, sí. También crea un nuevo pickler para cada tienda, por lo que almacenar dos objetos que se refieren al mismo tercer objeto significa que el tercer objeto se escabea dos veces (a menos que tus objetos no se salgan de esa manera, por supuesto, puedes definir el decapado exactamente como quieras) .) En otras palabras, la respuesta a su pregunta es 'len (pickle.dumps (obj))'. –

+0

Para la curiosidad gráfica, una vez probé y tracé esto para múltiples tipos integrados: http://stackoverflow.com/a/30008338/2087463 – tmthydvnprt

1

en el caso de una nueva instancia de la clase getsizeof() devolver el tamaño de una referencia a PyObject que es devuelto por la función C PyInstance_New()

si desea una lista de todos los cheque tamaño del objeto this.

Cuestiones relacionadas