понедельник, 6 марта 2017 г.

Шпаргалка по слотам (object.__slots__)

Для чего же нужны __slots__?
Снизить потребление памяти, экземпляром класса, путем ограничения количества поддерживаемых атрибутов.



При использовании __slots__, атрибут __dict__ будет недоступен. Если нужно иметь __dict__, его нужно добавить в __slots__, это будет означать что динамические атрибуты будут работать, тоесть добавляться в словарь __dict__.
Пример:


class A(object):
    """Класс со слотами.

    """
    __slots__ = ('a', 'b', 'c')



Соответственно при попытке доступа в __dict__ получим исключение:


In [1]: a = A()

In [2]: a.__dict__
---------------------------------------------------------
AttributeError        Traceback (most recent call last)
 in ()
----> 1 a.__dict__

AttributeError: 'A' object has no attribute '__dict__'

In [3]: dir(a)
Out[3]: 
['__class__',
 '__delattr__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__slots__',
 '__str__',
 '__subclasshook__',
 'a',
 'b',
 'c']



Если добавить __dict__ в слоты

class A(object):
    """Класс со слотами.

    """
    __slots__ = ('a', 'b', 'c', '__dict__')


Тогда:

In [4]: a = A()

In [5]: a.__dict__
Out[5]: {}

In [6]: dir(a)
Out[6]: 
['__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__slots__',
 '__str__',
 '__subclasshook__',
 'a',
 'b',
 'c']



Не имея атрибута __weakref__, экземпляры классов со __slots__ не поддерживают слабые ссылки на себя. Для поддержки слабых ссылок нужно добавить в перечень атрибутов __weakref__ по тому же принципу как и __dict__.

При наследовании от класса со слотами если не объявлены слоты, будет доступен __dict__ соответственно и динамические аттрибуты будут работать. При этом поведение атрибутов объявленых в слотах родительского класса не пеняется.


class A(object):
    """Класс со слотами.

    """
    __slots__ = ('a', 'b', 'c')


class B(A):
    """Класс без слотов, но наследующий класс со слотами.

    """
    pass

In [7]: b = B()

In [8]: dir(b)
Out[8]: 
['__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__slots__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'a',
 'b',
 'c']

In [9]: b.__dict__
Out[9]: {}

In [10]: b.a
---------------------------------------------------------
AttributeError        Traceback (most recent call last)
 in ()
----> 1 b.a

AttributeError: a




При наследовании от класса не имеющего __slots__, родителем будет создан __dict__


class C(object):
    """Класс без слотов.

    """
    pass


class D(C):
    """Класс со слотами, но наследующий класс без слотов.

    """
    __slots__ = ('q', 'w', 'e')


In [11]: c = C()

In [12]: d = D()

In [13]: c.__dict__
Out[13]: {}

In [14]: d.__dict__
Out[14]: {}

In [15]: d.__slots__
Out[15]: ('q', 'w', 'e')

In [16]: dir(c)
Out[16]: 
['__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__']

In [17]: dir(d)
Out[17]: 
['__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__slots__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'e',
 'q',
 'w']


И еще, нельзя определить __slots__ для классов наследующихся от встроенных типов переменной длины, например long, str и tuple. Точнее определить можно, но ненужно :) так как получите исключение TypeError
Читать дальше...