4
votes

Comprendre la taille de la classe, namedtuple et __slots__ dans Python 3.7

Après avoir regardé le discours sur la gestion de la mémoire Python de Nina Zahkarenko à Pycon2016 ( lien ), il semblait que la méthode dunder __slots__ était un outil pour réduire la taille de l'objet et accélérer la recherche d'attributs.

Je m'attendais à ce qu'une classe normale soit la plus grande, tandis qu'un __slots __ code L'approche> / namedtuple permettrait d'économiser de l'espace. Cependant, une expérience rapide avec sys.getsizeof () semble suggérer le contraire:

$ python3.7 example.py
Class: 56
Slots: 72
Named Tuple: 80

Sortie du terminal:

from collections import namedtuple
from sys import getsizeof

class Rectangle:
   '''A class based Rectangle, with a full __dict__'''
   def __init__(self, x, y, width, height):
      self.x = x
      self.y = y
      self.width = width
      self.height = height

class SlotsRectangle:
   '''A class based Rectangle with __slots__ defined for attributes'''
   __slots__ = ('x', 'y', 'width', 'height')

   def __init__(self, x, y, width, height):
      self.x = x
      self.y = y
      self.width = width
      self.height = height

NamedTupleRectangle = namedtuple('Rectangle', ('x', 'y', 'width', 'height'))
NamedTupleRectangle.__doc__ = 'A rectangle as an immutable namedtuple'

print(f'Class: {getsizeof(Rectangle(1,2,3,4))}')
print(f'Slots: {getsizeof(SlotsRectangle(1,2,3,4))}')
print(f'Named Tuple: {getsizeof(NamedTupleRectangle(1,2,3,4))}')


1 commentaires

getsizeof ne rapporte pas la taille des structures de données référencées par l'objet de classe; seulement l'objet de classe lui-même.


3 Réponses :


2
votes

La fonction sys.getsizeof () ne fait probablement pas ce que vous pensez qu'elle fait; cela ne fonctionne pas pour les objets complexes, comme les classes personnalisées.

Regardez cette réponse pour une méthode de calcul de la mémoire taille des objets; peut-être que cela vous aide. J'ai copié le code de cette réponse ici, mais l'explication complète se trouve dans la réponse que j'ai liée.

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)


1 commentaires

Merci pour la réponse @Ralf, je pense que sys.getsizeof () remplit mon cas d'utilisation. Mon objet rectangle contient quatre variables qui peuvent faire référence à des objets ailleurs sur le tas managé de Python. L'empreinte mémoire de x n'est pas pertinente pour moi, cela pourrait être une liste [] avec des milliers d'éléments. J'ai cru comprendre que l'attribut x n'était qu'une référence. Ce que je m'attendais à voir était une réduction de la charge mémoire lors de l'utilisation des slots par rapport à une classe sans.



0
votes

"Canaliser mon Raymond H intérieur", +1

Le problème avec les slots est que vous devez lire à propos des slots .

L'autre chose est qu'ils affectent la taille de la classe :

rect = Rectangle(1,2,3,4)
rect.extra_field = dict() # wild right?
print(f'(Object) Class: {getsizeof(rect)}') # still 56


0 commentaires

1
votes

Il existe une variante plus compacte avec la bibliothèque recordclass :

from recordclass import dataobject

class Rectangle(dataobject):
   x:int
   y:int
   width:int
   height:int

>>> r = Rectangle(1,2,3,4)
>>> print(sys.getsizeof(r))
48

Il a moins d'empreinte mémoire que celui basé sur __slots__ car il ne participe pas au ramasse-miettes cyclique (l'indicateur Py_TPFLAGS_HAVE_GC n'est pas défini, donc PyGC_Head (24 octets) ne pas besoin du tout).


0 commentaires