10. Método mágico __slots__¶
En Python cualquier clase tiene atributos de instancia. Por defecto se usa un diccionario para almacenar los atributos de un determinado objeto, y esto es algo muy útil que permite por ejemplo crear nuevos atributos en tiempo de ejecución.
Sin embargo, para clases pequeñas con atributos conocidos, puede llegar a resultar un cuello de botella. El uso del diccionario dict
desperdicia un montón de memoria RAM y Python no puede asignar una cantidad de memoria estática para almacenar los atributos. Por lo tanto, se come un montón de RAM si creas muchos objetos (del orden de miles o millones). Por suerte hay una forma de solucionar esto, haciendo uso de __slots__
, que permite decirle a Python que no use un diccionario y que solo asigne memoria para una cantidad fija de atributos. Aquí mostramos un ejemplo del uso de __slots__
:
Sin usar __slots__
:
class MiClase(object):
def __init__(self, nombre, identificador):
self.nombre = nombre
self.identificador = identificador
self.iniciar()
# ...
Usando __slots__
:
class MiClase(object):
__slots__ = ['nombre', 'identificador']
def __init__(self, nombre, identificador):
self.nombre = nombre
self.identificador = identificador
self.iniciar()
# ...
El segundo código reducirá el uso de RAM. En alguna ocasiones se han reportado reducciones de hasta un 40 o 50% usando esta técnica.
Como nota adicional, tal vez quieras echar un vistazo a PyPy, ya que hace este tipo de optimizaciones por defecto.
En el siguiente ejemplo puedes ver el uso exacto de memoria con y sin ``__slots__``hecho en IPython gracias a https://github.com/ianozsvald/ipython_memory_usage
Python 3.4.3 (default, Jun 6 2015, 13:32:34)
Type "copyright", "credits" or "license" for more information.
IPython 4.0.0 -- An enhanced Interactive Python.
? -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help -> Python's own help system.
object? -> Details about 'object', use 'object??' for extra details.
In [1]: import ipython_memory_usage.ipython_memory_usage as imu
In [2]: imu.start_watching_memory()
In [2] used 0.0000 MiB RAM in 5.31s, peaked 0.00 MiB above current, total RAM usage 15.57 MiB
In [3]: %cat slots.py
class MyClass(object):
__slots__ = ['name', 'identifier']
def __init__(self, name, identifier):
self.name = name
self.identifier = identifier
num = 1024*256
x = [MyClass(1,1) for i in range(num)]
In [3] used 0.2305 MiB RAM in 0.12s, peaked 0.00 MiB above current, total RAM usage 15.80 MiB
In [4]: from slots import *
In [4] used 9.3008 MiB RAM in 0.72s, peaked 0.00 MiB above current, total RAM usage 25.10 MiB
In [5]: %cat noslots.py
class MyClass(object):
def __init__(self, name, identifier):
self.name = name
self.identifier = identifier
num = 1024*256
x = [MyClass(1,1) for i in range(num)]
In [5] used 0.1758 MiB RAM in 0.12s, peaked 0.00 MiB above current, total RAM usage 25.28 MiB
In [6]: from noslots import *
In [6] used 22.6680 MiB RAM in 0.80s, peaked 0.00 MiB above current, total RAM usage 47.95 MiB
Se puede ver una clara reducción en el uso de RAM 9.3008 MiB vs 22.6680 MiB.