web-dev-qa-db-fra.com

Comment déterminer la taille d'un objet en Python?

En C, on peut trouver la taille d'un int, char, etc. Je veux savoir comment obtenir la taille d'objets comme une chaîne, un entier, etc. en Python.

Question connexe: Combien d'octets par élément y a-t-il dans une liste Python (Tuple)?

J'utilise un fichier XML contenant des champs de taille spécifiant la taille de la valeur. Je dois analyser ce XML et faire mon codage. Lorsque je souhaite modifier la valeur d'un champ particulier, je vérifie le champ de taille de cette valeur. Ici, je veux comparer si la nouvelle valeur que je dois entrer a la même taille que celle de XML. Je dois vérifier la taille de la nouvelle valeur. Dans le cas d'une chaîne, je peux dire que c'est la longueur. Mais en cas d'int, float, etc. Je suis confus.

560
user46646

Utilisez simplement la fonction sys.getsizeof définie dans le module sys.

sys.getsizeof(object[, default]):

Renvoie la taille d'un objet en octets. L'objet peut être n'importe quel type d'objet. Tous les objets intégrés renverront des résultats corrects, mais cela ne doit pas nécessairement être vrai pour les extensions tierces, car il s'agit d'une implémentation spécifique.

L'argument default permet de définir une valeur qui sera renvoyée si le type d'objet ne permet pas de récupérer la taille et provoquerait un TypeError.

getsizeof appelle la méthode __sizeof__ de l'objet et ajoute une surcharge supplémentaire pour le ramasse-miettes si l'objet est géré par le ramasse-miettes.

Exemple d'utilisation, dans python 3.0:

>>> import sys
>>> x = 2
>>> sys.getsizeof(x)
24
>>> sys.getsizeof(sys.getsizeof)
32
>>> sys.getsizeof('this')
38
>>> sys.getsizeof('this also')
48

Si vous êtes dans python <2.6 et que vous n'avez pas sys.getsizeof, vous pouvez utiliser ce module complet . Jamais utilisé cependant.

553
nosklo

Comment déterminer la taille d'un objet en Python?

La réponse "Il suffit d'utiliser sys.getsizeof" n'est pas une réponse complète.

Cette réponse fonctionne directement pour les objets intégrés, mais elle ne tient pas compte de ce que ces objets peuvent contenir, en particulier quels types, tels que les objets personnalisés, les n-uplets contient des listes, des dicts et des ensembles. Ils peuvent contenir des instances les unes des autres, ainsi que des nombres, des chaînes et d'autres objets.

Une réponse plus complète

En utilisant _ Python64 bits 64 bits de la distribution Anaconda, avec sys.getsizeof, j'ai déterminé la taille minimale des objets suivants et noté que les ensembles et les blocs préalloués afin que les objets vides ne croissent plus après un montant fixe (qui peut varier en fonction de la mise en œuvre de la langue):

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.

Comment interprétez-vous cela? Disons que vous avez un ensemble contenant 10 éléments. Si chaque élément a 100 octets, quelle est la taille de la structure de données? L'ensemble est 736 lui-même car il a été dimensionné une fois à 736 octets. Ensuite, vous ajoutez la taille des éléments, de sorte que 1736 octets au total

Quelques mises en garde concernant les définitions de fonction et de classe:

Notez que chaque définition de classe a une structure de proxy __dict__ (48 octets) pour la classe attrs. Chaque emplacement a un descripteur (comme un property) dans la définition de la classe.

Les instances placées commencent par 48 octets sur leur premier élément et augmentent de 8 chaque élément supplémentaire. Seuls les objets vides vides ont 16 octets et une instance sans données n'a pas beaucoup de sens.

De plus, chaque définition de fonction a des objets de code, peut-être des docstrings, et d'autres attributs possibles, même un __dict__.

Analyse Python 2.7, confirmée avec guppy.hpy et 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.

Notez que les dictionnaires ( mais pas les ensembles ) ont un représentation plus compacte dans Python 3.6

Je pense que 8 octets par élément supplémentaire à référencer a beaucoup de sens sur une machine 64 bits. Ces 8 octets indiquent l'emplacement en mémoire de l'élément contenu. Les 4 octets ont une largeur fixe pour unicode dans Python 2, si je me souviens bien, mais dans Python 3, str devient un unicode de largeur égale à la largeur maximale des caractères.

(Et pour plus sur les créneaux horaires, voir cette réponse )

Une fonction plus complète

Nous voulons une fonction qui recherche les éléments dans les listes, les tuples, les ensembles, les dicts, les obj.__dict__ et les obj.__slots__, ainsi que dans d’autres choses auxquelles nous n’avions peut-être pas encore pensé.

Nous voulons nous appuyer sur gc.get_referents pour effectuer cette recherche, car elle fonctionne au niveau C (ce qui la rend très rapide). L'inconvénient est que get_referents peut renvoyer des membres redondants, nous devons donc nous assurer de ne pas compter en double.

Les classes, les modules et les fonctions sont des singletons - ils existent une fois en mémoire. Nous ne sommes pas tellement intéressés par leur taille, car nous ne pouvons rien faire à leur sujet - ils font partie du programme. Nous éviterons donc de les compter s’il se trouve qu’ils sont référencés.

Nous allons utiliser une liste noire de types afin de ne pas inclure le programme entier dans notre nombre de tailles.

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

Pour mettre cela en contraste avec la fonction de liste blanche suivante, la plupart des objets savent comment se traverser eux-mêmes à des fins de récupération de place (ce qui est à peu près ce que nous recherchons lorsque nous voulons savoir combien coûtent certains objets en mémoire. Cette fonctionnalité est utilisée par gc.get_referents.) Cependant, cette mesure aura une portée beaucoup plus large que ce que nous avions prévu si nous ne faisons pas attention.

Par exemple, les fonctions en savent beaucoup sur les modules dans lesquels elles ont été créées.

Un autre point de contraste est que les chaînes qui sont des clés dans les dictionnaires sont généralement internées afin qu'elles ne soient pas dupliquées. Vérifier id(key) nous permettra également d’éviter le comptage des doublons, ce que nous faisons dans la section suivante. La solution de la liste noire ignore le comptage des clés qui sont des chaînes.

Types sur la liste blanche, visiteur récursif (ancienne implémentation)

Pour couvrir la plupart de ces types moi-même, au lieu de me fier au module gc, j’ai écrit cette fonction récursive pour tenter d’estimer la taille de la plupart des objets Python, y compris la plupart des fonctions intégrées, des types du module collections et des éléments personnalisés. types (fendus et autres).

Ce type de fonction offre un contrôle beaucoup plus fin sur les types que nous allons compter pour l'utilisation de la mémoire, mais présente le danger de laisser des types en dehors:

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)

Et je l'ai testé plutôt avec désinvolture (je devrais unittest):

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

Cette implémentation se décompose sur les définitions de classe et les définitions de fonction car nous ne cherchons pas tous leurs attributs, mais comme ils ne doivent exister qu'une seule fois en mémoire pour le processus, leur taille importe peu.

279
Aaron Hall

Pour les tableaux numpy, getsizeof ne fonctionne pas - pour moi, il retourne toujours 40 pour une raison quelconque:

from pylab import *
from sys import getsizeof
A = Rand(10)
B = Rand(10000)

Puis (en ipython):

In [64]: getsizeof(A)
Out[64]: 40

In [65]: getsizeof(B)
Out[65]: 40

Heureusement, cependant:

In [66]: A.nbytes
Out[66]: 80

In [67]: B.nbytes
Out[67]: 80000
77
Mike Dewar

Le module asizeof du package Pympler peut le faire.

Utilisez comme suit:

_from pympler import asizeof
asizeof.asizeof(my_object)
_

Contrairement à _sys.getsizeof_, il fonctionne pour vos objets créés par vous-même . Cela fonctionne même avec numpy.

_>>> asizeof.asizeof(Tuple('bcd'))
200
>>> asizeof.asizeof({'foo': 'bar', 'baz': 'bar'})
400
>>> asizeof.asizeof({})
280
>>> asizeof.asizeof({'foo':'bar'})
360
>>> asizeof.asizeof('foo')
40
>>> asizeof.asizeof(Bar())
352
>>> asizeof.asizeof(Bar().__dict__)
280
>>> A = Rand(10)
>>> B = Rand(10000)
>>> asizeof.asizeof(A)
176
>>> asizeof.asizeof(B)
80096
_

Comme mentionné ,

La taille du code (en octets) des objets tels que les classes, fonctions, méthodes, modules, etc. peut être incluse en définissant l'option _code=True_.

Et si vous avez besoin d'une autre vue sur les données en direct, Pympler

le module muppy est utilisé pour la surveillance en ligne d'une application Python et du module Class Tracker fournit une analyse hors ligne de la durée de vie de la sélection Python objets.

70
serv-inc

Cela peut être plus compliqué qu'il n'y paraît, selon la manière dont vous voulez compter les choses. Par exemple, si vous avez une liste d'ints, voulez-vous la taille de la liste contenant les références aux ints? (c’est-à-dire une liste uniquement, pas ce qui y est contenu) ou voulez-vous inclure les données réelles pointées, auquel cas vous devez traiter les références en double et comment éviter le double comptage lorsque deux objets contiennent des références à le même objet.

Vous voudrez peut-être consulter l'un des python profileurs de mémoire, tels que pysizer , pour voir s'ils répondent à vos besoins.

12
Brian

Ayant moi-même rencontré ce problème plusieurs fois, j'ai écrit une petite fonction (inspirée de la réponse de @ aaron-hall) et des tests qui font ce à quoi je m'attendais de la part de sys.getsizeof:

https://github.com/bosswissam/pysize

Si l'histoire vous intéresse, le voici

EDIT: Joindre le code ci-dessous pour faciliter la consultation. Pour voir le code le plus récent, veuillez consulter le lien github.

    import sys

    def get_size(obj, seen=None):
        """Recursively finds size of objects"""
        size = sys.getsizeof(obj)
        if seen is None:
            seen = set()
        obj_id = id(obj)
        if obj_id in seen:
            return 0
        # Important mark as seen *before* entering recursion to gracefully handle
        # self-referential objects
        seen.add(obj_id)
        if isinstance(obj, dict):
            size += sum([get_size(v, seen) for v in obj.values()])
            size += sum([get_size(k, seen) for k in obj.keys()])
        Elif hasattr(obj, '__dict__'):
            size += get_size(obj.__dict__, seen)
        Elif hasattr(obj, '__iter__') and not isinstance(obj, (str, bytes, bytearray)):
            size += sum([get_size(i, seen) for i in obj])
        return size
10
wissam

Voici un script rapide que j'ai écrit sur la base des réponses précédentes pour lister les tailles de toutes les variables

for i in dir():
    print (i, sys.getsizeof(eval(i)) )
8
alexey

Python 3.8 (T1 2019) modifiera certains des résultats de sys.getsizeof , comme annoncé ici par Raymond Hettinger:

Les conteneurs Python sont plus petits de 8 octets sur les versions 64 bits.

_Tuple ()  48 -> 40       
list  []  64 ->56
set()    224 -> 216
dict  {} 240 -> 232
_

Cela vient après numéro 33597 et Inada Naoki (methane) autour de Compact PyGC_Head, et PR 704

Cette idée réduit la taille de PyGC_Head à deux mots .

Actuellement, PyGC_Head prend trois mots ; _gc_prev_, _gc_next_ et _gc_refcnt_.

  • _gc_refcnt_ est utilisé lors de la collecte, pour la suppression d'essai.
  • _gc_prev_ est utilisé pour le suivi et le retrait du suivi.

Par conséquent, si nous pouvons éviter de suivre/supprimer le suivi lors de la suppression de la version d'évaluation, _gc_prev_ et _gc_refcnt_ peuvent partager le même espace mémoire.

Voir commit d5c875b :

Suppression d'un membre _Py_ssize_t_ de _PyGC_Head_.
La taille de tous les objets suivis par le GC (exemple: tuple, liste, dict) est réduite de 4 ou 8 octets.

7
VonC

Si vous n’avez pas besoin de la taille exacte de l’objet mais de la taille de celui-ci, une solution rapide (et sale) est de laisser le programme s'exécuter, dormir pendant une période prolongée et vérifier l'utilisation de la mémoire : Moniteur d’activité de Mac) par ce processus python particulier. Cela serait efficace lorsque vous essayez de trouver la taille d'un seul objet volumineux dans un processus python. Par exemple, je voulais récemment vérifier l'utilisation de la mémoire d'une nouvelle structure de données et la comparer à celle de la structure de données définie de Python. J'ai d'abord écrit les éléments (mots d'un grand livre du domaine public) dans un ensemble, puis vérifié la taille du processus, puis fait la même chose avec l'autre structure de données. J'ai découvert que le processus Python avec un jeu prend deux fois plus de mémoire que la nouvelle structure de données. Encore une fois, vous ne pourriez pas dire exactement que la mémoire utilisée par le processus est égale à la taille de l'objet. Au fur et à mesure que la taille de l'objet grossit, la taille de la mémoire consommée par le reste du processus devient négligeable par rapport à la taille de l'objet que vous essayez de surveiller.

1
picmate 涅