¿Cómo determino el tamaño de un objeto en Python?
La respuesta, "Solo use sys.getsizeof" no es una respuesta completa.
Esa respuesta hace el trabajo de orden interna se opone directamente, pero no tiene en cuenta lo que esos objetos pueden contener, en concreto, qué tipos, tales como objetos personalizados, tuplas, listas, predice y conjuntos contienen. Pueden contener instancias entre sí, así como números, cadenas y otros objetos.
Una respuesta más completa
Utilizando Python 3.6 de 64 bits de la distribución Anaconda, con sys.getsizeof, he determinado el tamaño mínimo de los siguientes objetos, y noto que los conjuntos y los dictos preasignan el espacio para que los vacíos no vuelvan a crecer hasta después de una cantidad establecida (que puede variar según la implementación del idioma):
Python 3:
Empty
Bytes type scaling notes
28 int +4 bytes about every 30 powers of 2
37 bytes +1 byte per additional byte
49 str +1-4 per additional character (depending on max width)
48 tuple +8 per additional item
64 list +8 for each additional
224 set 5th increases to 736; 21nd, 2272; 85th, 8416; 341, 32992
240 dict 6th increases to 368; 22nd, 1184; 43rd, 2280; 86th, 4704; 171st, 9320
136 func def does not include default args and other attrs
1056 class def no slots
56 class inst has a __dict__ attr, same scaling as dict above
888 class def with slots
16 __slots__ seems to store in mutable tuple-like structure
first slot grows to 48, and so on.
como interpretas esto? Bueno, digamos que tienes un conjunto con 10 elementos. Si cada elemento tiene 100 bytes cada uno, ¿qué tan grande es la estructura de datos completa? El conjunto es 736 en sí porque se ha dimensionado una vez a 736 bytes. Luego agrega el tamaño de los elementos, por lo que son 1736 bytes en total
Algunas advertencias para las definiciones de función y clase:
Tenga en cuenta que cada definición de clase tiene una __dict__
estructura proxy (48 bytes) para los atributos de clase. Cada ranura tiene un descriptor (como a property
) en la definición de clase.
Las instancias ranuradas comienzan con 48 bytes en su primer elemento y aumentan en 8 cada una adicional. Solo los objetos ranurados vacíos tienen 16 bytes, y una instancia sin datos tiene muy poco sentido.
Además, cada definición de función tiene objetos de código, tal vez cadenas de documentos y otros posibles atributos, incluso a __dict__
.
También tenga en cuenta que usamos sys.getsizeof()
porque nos importa el uso del espacio marginal, que incluye la sobrecarga de recolección de basura para el objeto, de los documentos :
getsizeof () llama al __sizeof__
método del objeto y agrega una sobrecarga adicional al recolector de basura si el objeto es administrado por el recolector de basura.
También tenga en cuenta que cambiar el tamaño de las listas (por ejemplo, agregarlas repetidamente) hace que preasignen espacio, de manera similar a los conjuntos y los dictados. Del código fuente listobj.c :
/* This over-allocates proportional to the list size, making room
* for additional growth. The over-allocation is mild, but is
* enough to give linear-time amortized behavior over a long
* sequence of appends() in the presence of a poorly-performing
* system realloc().
* The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
* Note: new_allocated won't overflow because the largest possible value
* is PY_SSIZE_T_MAX * (9 / 8) + 6 which always fits in a size_t.
*/
new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6);
Información histórica
Análisis de Python 2.7, confirmado con guppy.hpy
y sys.getsizeof
:
Bytes type empty + scaling notes
24 int NA
28 long NA
37 str + 1 byte per additional character
52 unicode + 4 bytes per additional character
56 tuple + 8 bytes per additional item
72 list + 32 for first, 8 for each additional
232 set sixth item increases to 744; 22nd, 2280; 86th, 8424
280 dict sixth item increases to 1048; 22nd, 3352; 86th, 12568 *
120 func def does not include default args and other attrs
64 class inst has a __dict__ attr, same scaling as dict above
16 __slots__ class with slots has no dict, seems to store in
mutable tuple-like structure.
904 class def has a proxy __dict__ structure for class attrs
104 old class makes sense, less stuff, has real dict though.
Tenga en cuenta que los diccionarios ( pero no los conjuntos ) tienen una representación más compacta en Python 3.6
Creo que 8 bytes por elemento adicional de referencia tiene mucho sentido en una máquina de 64 bits. Esos 8 bytes apuntan al lugar en la memoria donde está el elemento contenido. Los 4 bytes son de ancho fijo para Unicode en Python 2, si recuerdo correctamente, pero en Python 3, str se convierte en un Unicode de ancho igual al ancho máximo de los caracteres.
(Y para más información sobre tragamonedas, vea esta respuesta )
Una función más completa
Queremos una función que busque los elementos en listas, tuplas, conjuntos, dictos, obj.__dict__
's' y obj.__slots__
otras cosas en las que aún no hemos pensado.
Queremos confiar en gc.get_referents
hacer esta búsqueda porque funciona en el nivel C (lo que lo hace muy rápido). La desventaja es que get_referents puede devolver miembros redundantes, por lo que debemos asegurarnos de no contar dos veces.
Las clases, los módulos y las funciones son singletons: existen una vez en la memoria. No estamos tan interesados en su tamaño, ya que no hay mucho que podamos hacer al respecto, son parte del programa. Así que evitaremos contarlos si se hace referencia a ellos.
Vamos a utilizar una lista negra de tipos para no incluir todo el programa en nuestro recuento de tamaños.
import sys
from types import ModuleType, FunctionType
from gc import get_referents
# Custom objects know their class.
# Function objects seem to know way too much, including modules.
# Exclude modules as well.
BLACKLIST = type, ModuleType, FunctionType
def getsize(obj):
"""sum size of object & members."""
if isinstance(obj, BLACKLIST):
raise TypeError('getsize() does not take argument of type: '+ str(type(obj)))
seen_ids = set()
size = 0
objects = [obj]
while objects:
need_referents = []
for obj in objects:
if not isinstance(obj, BLACKLIST) and id(obj) not in seen_ids:
seen_ids.add(id(obj))
size += sys.getsizeof(obj)
need_referents.append(obj)
objects = get_referents(*need_referents)
return size
Para contrastar esto con la siguiente función incluida en la lista blanca, la mayoría de los objetos saben cómo atravesarse para la recolección de basura (que es aproximadamente lo que estamos buscando cuando queremos saber qué tan caros en memoria son ciertos objetos. Esta funcionalidad es utilizada por gc.get_referents
.) Sin embargo, esta medida tendrá un alcance mucho más amplio de lo que pretendíamos si no tenemos cuidado.
Por ejemplo, las funciones saben bastante sobre los módulos en los que se crean.
Otro punto de contraste es que las cadenas que son claves en los diccionarios generalmente están internadas para que no se dupliquen. La verificación id(key)
también nos permitirá evitar contar duplicados, lo que hacemos en la siguiente sección. La solución de la lista negra omite las teclas de conteo que son cadenas por completo.
Tipos incluidos en la lista blanca, visitante recursivo (implementación anterior)
Para cubrir la mayoría de estos tipos, en lugar de depender del módulo gc, escribí esta función recursiva para tratar de estimar el tamaño de la mayoría de los objetos de Python, incluidos la mayoría de los elementos incorporados, los tipos en el módulo de colecciones y los tipos personalizados (ranurado y de otro modo) .
Este tipo de función proporciona un control mucho más detallado sobre los tipos que vamos a contar para el uso de la memoria, pero tiene el peligro de omitir los tipos:
import sys
from numbers import Number
from collections import Set, Mapping, deque
try: # Python 2
zero_depth_bases = (basestring, Number, xrange, bytearray)
iteritems = 'iteritems'
except NameError: # Python 3
zero_depth_bases = (str, bytes, Number, range, bytearray)
iteritems = 'items'
def getsize(obj_0):
"""Recursively iterate to sum size of object & members."""
_seen_ids = set()
def inner(obj):
obj_id = id(obj)
if obj_id in _seen_ids:
return 0
_seen_ids.add(obj_id)
size = sys.getsizeof(obj)
if isinstance(obj, zero_depth_bases):
pass # bypass remaining control flow and return
elif isinstance(obj, (tuple, list, Set, deque)):
size += sum(inner(i) for i in obj)
elif isinstance(obj, Mapping) or hasattr(obj, iteritems):
size += sum(inner(k) + inner(v) for k, v in getattr(obj, iteritems)())
# Check for custom object instances - may subclass above too
if hasattr(obj, '__dict__'):
size += inner(vars(obj))
if hasattr(obj, '__slots__'): # can have __slots__ with __dict__
size += sum(inner(getattr(obj, s)) for s in obj.__slots__ if hasattr(obj, s))
return size
return inner(obj_0)
Y lo probé de manera bastante informal (debería probarlo unitariamente):
>>> getsize(['a', tuple('bcd'), Foo()])
344
>>> getsize(Foo())
16
>>> getsize(tuple('bcd'))
194
>>> getsize(['a', tuple('bcd'), Foo(), {'foo': 'bar', 'baz': 'bar'}])
752
>>> getsize({'foo': 'bar', 'baz': 'bar'})
400
>>> getsize({})
280
>>> getsize({'foo':'bar'})
360
>>> getsize('foo')
40
>>> class Bar():
... def baz():
... pass
>>> getsize(Bar())
352
>>> getsize(Bar().__dict__)
280
>>> sys.getsizeof(Bar())
72
>>> getsize(Bar.__dict__)
872
>>> sys.getsizeof(Bar.__dict__)
280
Esta implementación desglosa las definiciones de clase y las definiciones de función porque no buscamos todos sus atributos, pero dado que solo deberían existir una vez en la memoria para el proceso, su tamaño realmente no importa demasiado.