Python что такое slots

Как и когда использовать __slots__ в python

Каждый объект python имеет атрибут _dict_, который представляет собой словарь, содержащий
все остальные атрибуты. Например, когда вы набираете self.attr, python на самом деле делает следующее
self.dict[‘attr’].

Как вы можете себе представить, использование словаря для хранения атрибутов занимает некоторое дополнительное место
& время для доступа к нему.

Что если вы уже знаете, какие атрибуты будут у вашего класса. Тогда
использование _dict_ для хранения атрибутов не кажется хорошей идеей, поскольку нам не нужна
нам не нужна динамическая природа dicts .

Здесь на помощь приходят слоты .

Что такое слоты в python

_slots_ — это переменная класса, которая позволяет нам объявлять атрибуты класса.
класса в явном виде и отрицает создание _dict_ и
_weakref_ для экземпляров объектов.

Ожидаемые атрибуты могут быть назначены _slots_ в виде строки, итерабельной переменной,
или последовательность строк с именами переменных, используемых экземпляром.

Экономия места за счет отсутствия _dict_ может быть значительной. Слоты также
улучшают скорость поиска атрибутов.

Использование слотов

Python использует значительно более компактное внутреннее представление для
экземпляров, когда вы определяете _слоты_. Вместо словаря каждый экземпляр
создается вокруг небольшого массива фиксированного размера, похожего на кортеж или список.

В результате использования слотов вы больше не можете добавлять новые атрибуты к
экземплярам;
вы ограничены именами атрибутов, указанными в спецификаторе _slots_.( Вы можете обойти это, добавив _dlots_.
можно обойти это, добавив _dict_ к _slot_ . Диктант будет
инициализироваться только при добавлении динамического атрибута).

Пример использования слотов:

 # A email class with out using slots class Email : def __init__(self,subject,to,message) : self.subject = subject self.message = message self.to = to class EmailWithSlots : __slots__ = ('subject','to','message') def __init__(self,subject,to,message) : self.subject = subject self.message = message self.to = to email = EmailWithSlots('test','me@gmail.com','testing slots') email.subject # >> test email.__dict__ # cant access __dict__ because its not created # AttributeError Traceback (most recent call last) # in # ----> 1 email.__dict__ # #AttributeError: 'EmailWithSlots' object has no attribute '__dict__' email.from = "aabid@gmail.com" # cant add an atribute that not in __slots__ # --------------------------------------------------------------------------- # AttributeError Traceback (most recent call last) # in # ----> email.from = "aabid@gmail.com" # #AttributeError: 'EmailWithSlots' object has no attribute 'from' 

Плюсы и минусы слотов

Плюсы

  • использование _слотов_ ускоряет доступ к атрибутам .
  • _slots_ уменьшает использование памяти

Минусы

  • Фиксированные атрибуты (Вы можете обойти это, добавив _dict_ к _slots_ . Диктант будет инициализироваться только при добавлении динамического атрибута )
  • _slots_ реализованы на уровне класса путем создания дескрипторов для каждого имени переменной. В результате атрибуты класса не могут быть использованы для установки значений по умолчанию для переменных экземпляра, определенных _slots_; в противном случае атрибут класса перезапишет назначение дескриптора.

Наследование со слотами .

Работа со слотами и наследованием немного сложна и требует внимания.
Вот как:

 class BaseClass : __slots__ = ['x','y','z'] class Inherited(BaseClass) : pass Inherited.__slots__ #>> ['x', 'y', 'z'] 
  • Если в подклассе не указаны слоты (пустые или новые) . Подкласс получит атрибуты _dict_ и _weakref_ в дополнение к слотам родительского класса.
 class BaseClass : __slots__ = ['x','y','z'] class Inherited(BaseClass) : pass class InheritedWithSlots(BaseClass) : __slots__ = () Inherited.__slots__ #>> ['x', 'y', 'z'] Inherited.__dict__ #>> <> InheritedWithSlots().__dict__ # AttributeError 
  • Непустые _слоты_ не работают для классов, производных от встроенных типов «переменной длины», таких как int, bytes и tuple.
 # this works fine because we are not using any additional slots in subclass class MyInt(int): __slots__ = () # This will panic because we are adding non-empty slots . class NewInt(int) : __slots__ = (x,y) #TypeError: nonempty __slots__ not supported for subtype of 'int' 
 #lets have three slotted base classes class foo: __slots__ = ("x","y") class bar : __slots__ = ("a","b") class baz : __slots__=() # this will raise TypeError as we are inheriting from two classes # with nonempty slots class foobar(foo,bar) : pass #>>TypeError: multiple bases have instance lay-out conflict # This shall work as only one of the inherited class has nonempty slots class FooBaz(foo,bar) : pass 

Заключение :

_слоты_ позволяют нам явно указать, какие переменные экземпляра следует ожидать в
объект

Что дает нам следующие преимущества

Типичный случай использования

Для классов, которые в основном служат в качестве простых структур данных, часто можно значительно уменьшить
объем памяти экземпляров, добавив атрибут _slots_ в
определению класса.

Когда слоты — плохая идея

  • Избегайте их, когда вы хотите выполнить _классовое_ присвоение с другим классом, у которого их нет (и вы не можете их добавить), если только расположение слотов не идентично. (Мне очень интересно узнать, кто и зачем это делает).
  • Избегайте их, если вы хотите подклассифицировать встроенные элементы переменной длины, такие как long, tuple или str, и хотите добавить к ним атрибуты.
  • Избегайте их, если вы настаиваете на предоставлении значений по умолчанию через атрибуты класса для переменных экземпляра.

Спасибо, что дочитали до конца, счастливого кодинга.

Источник

Использование slots | Python

Эта статья вдохновлена моим обучением. Когда я только начинал свой Python-way, на одном из форумов увидел новое для себя понятие — слоты. Но сколько я не искал, в сети было крайне мало статей на эту тему, поэтому понять и осознать слоты было достаточно сложно. Данная статья призвана помочь начинающим в этой теме, но даже опытные разработчики, уверен, найдут здесь нечто новое.

Когда мы создаем объекты для классов, требуется память, а атрибут хранится в виде словаря (в dict). В случае, если нам нужно выделить тысячи объектов, это займет достаточно много места в памяти.

К счастью, есть выход — слоты, они обеспечивают специальный механизм уменьшения размера объектов. Это концепция оптимизации памяти на объектах. Также, использование слотов позволяет нам ускорить доступ к атрибутам.

Пример объекта python без слотов:

class NoSlots: def __init__(self): self.a = 1 self.b = 2 if __name__ == "__main__": ns = NoSlots() print(ns.__dict__)

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

Использование slots уменьшает потери пространства и ускоряет работу программы, выделяя пространство для фиксированного количества атрибутов.

Пример объекта python со слотами:

class WithSlots(object): __slots__ = ['a', 'b'] def __init__(self): self.a = 1 self.b = 2 if __name__ == "__main__": ws = WithSlots() print(ws.__slots__) 

Пример python, если мы используем dict:

class WithSlots: __slots__ = ['a', 'b'] def init(self): self.a = 1 self.b = 2 if __name__ == "__main__": ws = WithSlots() print(ws.__dict__) 
AttributeError: объект WithSlots не имеет атрибута '__dict__'

Как мы видим, будет вызвана ошибка AttributeError. Не сложно догадаться, что раз мы не можем вызвать dict, то и создавать новые атрибуты мы не сможем.

Это что касается потребляемой памяти, а теперь давайте рассмотрим скорость доступа к атрибутам:

class Foo(object): __slots__ = ('foo',) class Bar(object): pass def get_set_delete(obj): obj.foo = 'foo' obj.foo del obj.foo def test_foo(): get_set_delete(Foo()) def test_bar(): get_set_delete(Bar())

И с помощью модуля timeit оценим время выполнения:

>>> import timeit >>> min(timeit.repeat(test_foo)) 0.2567792439949699 >>> min(timeit.repeat(test_bar)) 0.34515008199377917

Таким образом, получается, что класс с использованием slots примерно на 25-30 % быстрее на операциях доступа к атрибутам. Конечно, этот показатель может меняться в зависимости от версии языка или ОС на которой запускается программа.

Как мы видим, использовать слоты довольно просто, но есть и некоторые подводные камни. Например, наследование. Нужно понимать, что значение slots наследуется, однако это не предотвращает создание dict.

Таким образом, дочерние классы не будут запрещать добавлять динамические атрибуты, и добавляться они будут в__dict__, со всеми вытекающими расходами (по памяти и производительности).

class SlotsClass: __slots__ = ('foo', 'bar') class ChildSlotsClass(SlotsClass): pass >>> obj = ChildSlotsClass() >>> obj.__slots__ ('foo', 'bar') >>> obj.foo = 5 >>> obj.something_new = 3 >>> obj.__dict__

Если нам нужно, чтобы и дочерний класс тоже был ограничен слотами, там придётся и в нём присвоить значение атрибуту slots. Кстати, дублировать уже указанные в родительском классе слоты не нужно.

class SlotsClass: __slots__ = ('foo', 'bar') class ChildSlotsClass(SlotsClass): __slots__ = ('baz',) >>> obj = ChildSlotsClass() >>> obj.foo = 5 >>> obj.baz = 6 >>> obj.something_new = 3 Traceback (most recent call last): File "python", line 12, in AttributeError: 'ChildSlotsClass' object has no attribute 'something_new'

Гораздо хуже обстоит дело с множественным наследованием. Если у нас есть два родительских класса, у каждого из которых определены слоты, то попытка создать дочерний класс, обречена на провал.

class BaseOne: __slots__ = ('param1',) class BaseTwo: __slots__ = ('param2',) >>> class Child(BaseOne, BaseTwo): __slots__ = ()

Traceback (most recent call last):
File «», line 1, in
class Child(BaseOne, BaseTwo): __slots__ = ()
TypeError: Error when calling the metaclass bases
multiple bases have instance lay-out conflict

Один из способов решения этой проблемы — абстрактные классы. Но об этом думаю поговорим в следующий раз.

Ну и под конец важные выводы:

  • Без переменной словаря dict , экземплярам нельзя назначить атрибуты, не указанные в определении slots . При попытке присвоения имени переменной, не указанной в списке, вы получите ошибку AttributeError . Если требуется динамическое присвоение новых переменных, добавьте значение ‘dict’ в объявлении атрибута slots .
  • Атрибуты slots , объявленные в родительских классах, доступны в дочерних классах. Однако дочерние подклассы получат dict , если они не переопределяют slots .
  • Если класс определяет слот, также определенный в базовом классе, переменная экземпляра, определенная слотом базового класса, недоступна. Это приводит к неоднозначному поведению программы.
  • Атрибут slots не работает для классов, наследованных, от встроенных типов переменной длины, таких как int , bytes и tuple .
  • Атрибуту slots может быть назначен любой нестроковый итерируемый объект. Могут использоваться словари, значениям, соответствующим каждому ключу, может быть присвоено особое значение.
  • Назначение class работает, если оба класса имеют одинаковые slots .
  • Может использоваться множественное наследование с несколькими родительскими классами с разделением на слоты, но только одному родительскому элементу разрешено иметь атрибуты, созданные с помощью слотов (другие классы должны иметь макеты пустых слотов), нарушение вызовет исключение TypeError .

Надеюсь всё было просто и понятно, и теперь вы чаще станете использовать slots у себя в проектах.

Жду вашего мнения на эту тему, всем удачи!

Источник

Читайте также:  Php array get all index
Оцените статью