• en
  • Language: ru
  • Documentation version: latest

10. __slots___Магия

В Python каждый класс может иметь атрибуты экземпляра. По умолчанию Python использует dict для хранения атрибутов экземпляра объекта. Это очень удобно, так как позволяет задавать произвольные новые атрибуты во время выполнения.

Однако для небольших классов с известными атрибутами это может оказаться узким местом. dict тратит много оперативной памяти. Python не может просто выделить статический объем памяти при создании объекта для хранения всех атрибутов. Поэтому он отсасывает много оперативной памяти, если вы создаете много объектов (я говорю о тысячах и миллионах). Тем не менее, есть способ обойти эту проблему. Он заключается в использовании __slots__, чтобы указать Python не использовать dict, а выделять место только для фиксированного набора атрибутов. Вот пример с и без __slots__:

Без __slots__:

class MyClass(object):
    def __init__(self, name, identifier):
        self.name = name
        self.identifier = identifier
        self.set_up()
    # ...

With __slots__:

class MyClass(object):
    __slots__ = ['name', 'identifier']
    def __init__(self, name, identifier):
        self.name = name
        self.identifier = identifier
        self.set_up()
    # ...

Второй фрагмент кода снизит нагрузку на оперативную память. Некоторые люди наблюдали снижение использования оперативной памяти почти на 40-50% благодаря использованию этой техники.

В качестве примечания, вы можете попробовать PyPy. Он выполняет все эти оптимизации по умолчанию.

Ниже приведен пример, показывающий точное использование памяти с и без __slots__, выполненный в IPython благодаря 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