Подтвердить что ты не робот

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

Какова цель __slots__ в Python - особенно когда я хотел бы использовать его, а когда нет?

4b9b3361

Ответ 1

В Python, какова цель __slots__ и в каких случаях следует избегать этого?

TL;DR:

Специальный атрибут __slots__ позволяет вам явно указать, какие атрибуты экземпляров ожидаются для ваших экземпляров объекта, с ожидаемыми результатами:

  1. более быстрый доступ к атрибутам.
  2. экономия места в памяти.

Экономия пространства от

  1. Хранение ссылок на значения в слотах вместо __dict__.
  2. Отказ в __dict__ и __weakref__ если родительские классы отрицают их, и вы объявляете __slots__.

Быстрые предостережения

Небольшое предостережение, вы должны объявить определенный слот только один раз в дереве наследования. Например:

class Base:
    __slots__ = 'foo', 'bar'

class Right(Base):
    __slots__ = 'baz', 

class Wrong(Base):
    __slots__ = 'foo', 'bar', 'baz'        # redundant foo and bar

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

>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(64, 80)

Наибольшее предостережение касается множественного наследования - несколько "родительских классов с непустыми слотами" не могут быть объединены.

Чтобы учесть это ограничение, следуйте рекомендациям: Вычтите все абстракции всех родителей, кроме одного или всех, от которых наследует их конкретный класс, соответственно, и ваш новый конкретный класс - предоставляя абстракции (ям) пустые слоты (как абстрактные базовые классы в стандартная библиотека).

См. Раздел о множественном наследовании ниже для примера.

Требования:

  • Чтобы иметь атрибуты с именами в __slots__ для фактического хранения в слотах вместо __dict__, класс должен наследовать от object.

  • Чтобы предотвратить создание __dict__, вы должны наследовать от object и все классы в наследовании должны объявлять __slots__ и ни один из них не может иметь запись '__dict__'.

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

Зачем использовать __slots__: более быстрый доступ к атрибутам.

Создатель Python, Гвидо ван Россум, утверждает, что он действительно создал __slots__ для более быстрого доступа к атрибутам.

Тривиально продемонстрировать значительно более быстрый доступ:

import timeit

class Foo(object): __slots__ = 'foo',

class Bar(object): pass

slotted = Foo()
not_slotted = Bar()

def get_set_delete_fn(obj):
    def get_set_delete():
        obj.foo = 'foo'
        obj.foo
        del obj.foo
    return get_set_delete

а также

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085

Слотный доступ почти на 30% быстрее в Python 3.5 на Ubuntu.

>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342

В Python 2 на Windows я измерил его примерно на 15% быстрее.

Зачем использовать __slots__: Экономия памяти

Другая цель __slots__ - уменьшить пространство в памяти, которое занимает каждый экземпляр объекта.

Мой собственный вклад в документацию ясно указывает причины этого:

Пространство, сэкономленное при использовании __dict__ может быть значительным.

SQLAlchemy связывает большую экономию памяти с __slots__.

Чтобы убедиться в этом, с помощью дистрибутива Anaconda Python 2.7 в Ubuntu Linux с guppy.hpy (он же heapy) и sys.getsizeof размер экземпляра класса без объявленного __slots__ и ничего более составляет 64 байта. Это не включает __dict__. __dict__ раз спасибо Python за ленивую оценку, __dict__ по-видимому, не вызывается, пока на него не ссылаются, но классы без данных обычно бесполезны. При __dict__ атрибут __dict__ содержит минимум 280 байт.

Напротив, экземпляр класса с __slots__ объявленным как () (без данных), составляет всего 16 байтов, и всего 56 байтов с одним элементом в слотах, 64 с двумя.

Для 64-битного Python я проиллюстрирую потребление памяти в байтах в Python 2.7 и 3.6, для __slots__ и __dict__ (не определены слоты) для каждой точки, где dict возрастает в 3.6 (за исключением атрибутов 0, 1 и 2):

       Python 2.7             Python 3.6
attrs  __slots__  __dict__*   __slots__  __dict__* | *(no slots defined)
none   16         56 + 272†   16         56 + 112† | †if __dict__ referenced
one    48         56 + 272    48         56 + 112
two    56         56 + 272    56         56 + 112
six    88         56 + 1040   88         56 + 152
11     128        56 + 1040   128        56 + 240
22     216        56 + 3344   216        56 + 408     
43     384        56 + 3344   384        56 + 752

Итак, несмотря на меньшие требования в Python 3, мы видим, как хорошо __slots__ масштабируют экземпляры, чтобы сэкономить нам память, и это основная причина, по которой вы захотите использовать __slots__.

Просто для полноты моих заметок обратите внимание, что в пространстве имен класса существует разовая стоимость одного слота: 64 байта в Python 2 и 72 байта в Python 3, поскольку в слотах используются дескрипторы данных, такие как свойства, называемые "члены".

>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72

Демонстрация __slots__:

Чтобы отрицать создание __dict__, вы должны __dict__ object подкласса:

class Base(object): 
    __slots__ = ()

сейчас:

>>> b = Base()
>>> b.a = 'a'
Traceback (most recent call last):
  File "<pyshell#38>", line 1, in <module>
    b.a = 'a'
AttributeError: 'Base' object has no attribute 'a'

Или подкласс другого класса, который определяет __slots__

class Child(Base):
    __slots__ = ('a',)

и сейчас:

c = Child()
c.a = 'a'

но:

>>> c.b = 'b'
Traceback (most recent call last):
  File "<pyshell#42>", line 1, in <module>
    c.b = 'b'
AttributeError: 'Child' object has no attribute 'b'

Чтобы разрешить создание __dict__ во время создания подклассов объектов со '__dict__', просто добавьте '__dict__' к __slots__ (обратите внимание, что слоты упорядочены, и вы не должны повторять слоты, которые уже находятся в родительских классах):

class SlottedWithDict(Child): 
    __slots__ = ('__dict__', 'b')

swd = SlottedWithDict()
swd.a = 'a'
swd.b = 'b'
swd.c = 'c'

а также

>>> swd.__dict__
{'c': 'c'}

Или вам даже не нужно объявлять __slots__ в своем подклассе, и вы все равно будете использовать слоты от родителей, но не ограничивать создание __dict__:

class NoSlots(Child): pass
ns = NoSlots()
ns.a = 'a'
ns.b = 'b'

А также:

>>> ns.__dict__
{'b': 'b'}

Однако __slots__ может вызвать проблемы для множественного наследования:

class BaseA(object): 
    __slots__ = ('a',)

class BaseB(object): 
    __slots__ = ('b',)

Поскольку создание дочернего класса от родителей с обоими непустыми слотами завершается неудачно:

>>> class Child(BaseA, BaseB): __slots__ = ()
Traceback (most recent call last):
  File "<pyshell#68>", line 1, in <module>
    class Child(BaseA, BaseB): __slots__ = ()
TypeError: Error when calling the metaclass bases
    multiple bases have instance lay-out conflict

Если вы столкнулись с этой проблемой, вы можете просто удалить __slots__ из родителей, или, если у вас есть контроль над родителями, дать им пустые слоты или рефакторировать абстракции:

from abc import ABC

class AbstractA(ABC):
    __slots__ = ()

class BaseA(AbstractA): 
    __slots__ = ('a',)

class AbstractB(ABC):
    __slots__ = ()

class BaseB(AbstractB): 
    __slots__ = ('b',)

class Child(AbstractA, AbstractB): 
    __slots__ = ('a', 'b')

c = Child() # no problem!

Добавьте '__dict__' к __slots__ чтобы получить динамическое назначение:

class Foo(object):
    __slots__ = 'bar', 'baz', '__dict__'

и сейчас:

>>> foo = Foo()
>>> foo.boink = 'boink'

Таким образом, с '__dict__' в слотах мы теряем некоторые преимущества по размеру с преимуществом динамического назначения и '__dict__' слоты для имен, которые мы ожидаем.

Когда вы наследуете от объекта, который не является слотом, вы получаете семантику того же рода, когда используете __slots__ - имена в __slots__ указывают на значения __slots__, в то время как любые другие значения помещаются в экземпляр __dict__.

Избегать __slots__ потому что вы хотите иметь возможность добавлять атрибуты на лету, на самом деле не является хорошей причиной - просто добавьте "__dict__" в свои __slots__ если это требуется.

Вы также можете явно добавить __weakref__ к __slots__ если вам нужна эта функция.

Установите пустой кортеж при создании подкласса именованного кортежа:

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

from collections import namedtuple
class MyNT(namedtuple('MyNT', 'bar baz')):
    """MyNT is an immutable and lightweight object"""
    __slots__ = ()

использование:

>>> nt = MyNT('bar', 'baz')
>>> nt.bar
'bar'
>>> nt.baz
'baz'

И попытка назначить неожиданный атрибут вызывает AttributeError потому что мы предотвратили создание __dict__:

>>> nt.quux = 'quux'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyNT' object has no attribute 'quux'

Вы можете разрешить создание __dict__, отключив __slots__ =(), но не можете использовать непустые __slots__ с подтипами кортежа.

Самое большое предостережение: множественное наследование

Даже если непустые слоты одинаковы для нескольких родителей, их нельзя использовать вместе:

class Foo(object): 
    __slots__ = 'foo', 'bar'
class Bar(object):
    __slots__ = 'foo', 'bar' # alas, would work if empty, i.e. ()

>>> class Baz(Foo, Bar): pass
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
    multiple bases have instance lay-out conflict

Использование пустого __slots__ в родительском __slots__ по-видимому, обеспечивает наибольшую гибкость, позволяя дочернему '__dict__' выбрать для предотвращения или разрешения (добавив '__dict__' для получения динамического назначения, см. Раздел выше) создание __dict__:

class Foo(object): __slots__ = ()
class Bar(object): __slots__ = ()
class Baz(Foo, Bar): __slots__ = ('foo', 'bar')
b = Baz()
b.foo, b.bar = 'foo', 'bar'

Вам не нужно иметь слоты - поэтому, если вы добавите их и удалите их позже, это не должно вызвать каких-либо проблем.

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

Для демонстрации, во-первых, давайте создадим класс с кодом, который мы хотели бы использовать при множественном наследовании.

class AbstractBase:
    __slots__ = ()
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def __repr__(self):
        return f'{type(self).__name__}({repr(self.a)}, {repr(self.b)})'

Мы могли бы использовать вышеперечисленное напрямую, унаследовав и объявив ожидаемые слоты:

class Foo(AbstractBase):
    __slots__ = 'a', 'b'

Но нас это не волнует, это тривиальное одиночное наследование, нам нужен другой класс, от которого мы могли бы также наследовать, может быть, с атрибутом noisy:

class AbstractBaseC:
    __slots__ = ()
    @property
    def c(self):
        print('getting c!')
        return self._c
    @c.setter
    def c(self, arg):
        print('setting c!')
        self._c = arg

Теперь, если на обеих базах были непустые слоты, мы не смогли бы сделать следующее. (На самом деле, если бы мы захотели, мы могли бы дать непустые слоты AbstractBase a и b и исключить их из нижеприведенного объявления - оставить их было бы неправильно):

class Concretion(AbstractBase, AbstractBaseC):
    __slots__ = 'a b _c'.split()

И теперь у нас есть функциональность с помощью множественного наследования, и мы все еще можем отрицать реализацию __dict__ и __weakref__:

>>> c = Concretion('a', 'b')
>>> c.c = c
setting c!
>>> c.c
getting c!
Concretion('a', 'b')
>>> c.d = 'd'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Concretion' object has no attribute 'd'

Другие случаи, чтобы избежать слотов:

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

Возможно, вы сможете найти дополнительные предостережения в остальной части документации __slots__ ( самые __slots__ документы по 3.7 для разработчиков), в которую я внес значительный вклад в последнее время.

Критика других ответов

Нынешние топ-ответы приводят устаревшую информацию, они довольно волнистые и в некоторых важных аспектах не попадают в цель.

Не "используйте __slots__ только при __slots__ множества объектов"

Я цитирую:

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

Абстрактные базовые классы, например, из модуля collections, не создаются, но для них объявляется __slots__.

Зачем?

Если пользователь хочет отрицать __dict__ или __weakref__, эти вещи не должны быть доступны в родительских классах.

__slots__ способствует повторному использованию при создании интерфейсов или миксинов.

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

__slots__ не ломает травление

Когда вы выбираете щелевой объект, вы можете обнаружить, что он жалуется на вводящую в заблуждение TypeError:

>>> pickle.loads(pickle.dumps(f))
TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled

Это на самом деле неверно. Это сообщение приходит от самого старого протокола, который используется по умолчанию. Вы можете выбрать последний протокол с аргументом -1. В Python 2.7 это будет 2 (который был введен в 2.3), а в 3.6 это 4.

>>> pickle.loads(pickle.dumps(f, -1))
<__main__.Foo object at 0x1129C770>

в Python 2.7:

>>> pickle.loads(pickle.dumps(f, 2))
<__main__.Foo object at 0x1129C770>

в Python 3.6

>>> pickle.loads(pickle.dumps(f, 4))
<__main__.Foo object at 0x1129C770>

Так что я бы помнил об этом, так как это решенная проблема.

Критика принятого ответа (до 2 октября 2016 г.)

Первый абзац - наполовину короткое объяснение, наполовину предсказательный. Здесь единственная часть, которая на самом деле отвечает на вопрос

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

Вторая половина - это желаемое за действительное и нестандартное мышление:

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

Python фактически делает что-то похожее на это, только создавая __dict__ при обращении к нему, но создавая множество объектов без данных, довольно нелепо.

Второй абзац упрощает и пропускает реальные причины, чтобы избежать __slots__. Ниже не реальная причина, чтобы избежать слотов (по фактическим причинам, см. Остальную часть моего ответа выше.):

Они изменяют поведение объектов, имеющих слоты, таким образом, что они могут быть использованы злоупотребляющими людьми и статичными типизаторами.

Затем он продолжает обсуждать другие способы достижения этой порочной цели с Python, не обсуждая ничего общего с __slots__.

Третий абзац более желаемое за действительное. Вместе это в основном некондиционный контент, который автор даже не написал, и он вносит вклад в боеприпасы для критиков сайта.

Доказательства использования памяти

Создайте несколько нормальных объектов и щелевых объектов:

>>> class Foo(object): pass
>>> class Bar(object): __slots__ = ()

Создайте миллион из них:

>>> foos = [Foo() for f in xrange(1000000)]
>>> bars = [Bar() for b in xrange(1000000)]

Проверьте с помощью guppy.hpy().heap():

>>> guppy.hpy().heap()
Partition of a set of 2028259 objects. Total size = 99763360 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0 1000000  49 64000000  64  64000000  64 __main__.Foo
     1     169   0 16281480  16  80281480  80 list
     2 1000000  49 16000000  16  96281480  97 __main__.Bar
     3   12284   1   987472   1  97268952  97 str
...

Получите доступ к обычным объектам и их __dict__ и проверьте еще раз:

>>> for f in foos:
...     f.__dict__
>>> guppy.hpy().heap()
Partition of a set of 3028258 objects. Total size = 379763480 bytes.
 Index  Count   %      Size    % Cumulative  % Kind (class / dict of class)
     0 1000000  33 280000000  74 280000000  74 dict of __main__.Foo
     1 1000000  33  64000000  17 344000000  91 __main__.Foo
     2     169   0  16281480   4 360281480  95 list
     3 1000000  33  16000000   4 376281480  99 __main__.Bar
     4   12284   0    987472   0 377268952  99 str
...

Это согласуется с историей Python, начиная с унификации типов и классов в Python 2.2

Если вы создаете подкласс встроенного типа, в экземпляры автоматически добавляется дополнительное пространство для размещения __dict__ и __weakrefs__. (__dict__ не инициализируется до тех пор, пока вы его не используете, поэтому вам не нужно беспокоиться о пространстве, занимаемом пустым словарем для каждого создаваемого вами экземпляра.) Если вам не нужно это дополнительное пространство, вы можете добавить фразу " __slots__ = [] "в ваш класс.

Ответ 2

Цитата Jacob Hallen:

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

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

Сделать CPython достаточно умным для экономии пространства без __slots__, является основным что, вероятно, почему это не входит в список изменений для P3k (пока).

Ответ 3

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

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

Ответ 4

Каждый объект python имеет атрибут __dict__ atttribute, который является словарем, содержащим все остальные атрибуты. например при вводе self.attr python фактически выполняет self.__dict__['attr']. Как вы можете себе представить, использование словаря для хранения атрибута занимает некоторое дополнительное пространство и время для его доступа.

Однако, когда вы используете __slots__, любой объект, созданный для этого класса, не будет иметь атрибут __dict__. Вместо этого весь доступ к атрибутам выполняется непосредственно через указатели.

Поэтому, если вы хотите создать структуру стиля C, а не полноценный класс, вы можете использовать __slots__ для уплотнения размера объектов и сокращения времени доступа к атрибуту. Хорошим примером является класс Point, содержащий атрибуты x и y. Если у вас будет много очков, вы можете попробовать использовать __slots__, чтобы сохранить некоторую память.

Ответ 5

В дополнение к другим ответам, вот пример использования __slots__:

>>> class Test(object):   #Must be new-style class!
...  __slots__ = ['x', 'y']
... 
>>> pt = Test()
>>> dir(pt)
['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__', 
 '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', 
 '__repr__', '__setattr__', '__slots__', '__str__', 'x', 'y']
>>> pt.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: x
>>> pt.x = 1
>>> pt.x
1
>>> pt.z = 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute 'z'
>>> pt.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute '__dict__'
>>> pt.__slots__
['x', 'y']

Итак, чтобы реализовать __slots__, он занимает только дополнительную строку (и делает ваш класс классом нового стиля, если он еще не был). Таким образом, вы можете уменьшить объем памяти этих классов в 5 раз, за счет необходимости писать собственный код рассола, если и когда это станет необходимо.

Ответ 6

Слоты очень полезны для вызовов библиотеки, чтобы исключить "named method dispatch" при выполнении вызовов функций. Это упоминается в документации SWIG . Для высокопроизводительных библиотек, которые хотят уменьшить накладные расходы функций для обычно называемых функций с использованием слотов, намного быстрее.

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

Ответ 7

Атрибут экземпляра класса имеет 3 свойства: экземпляр, имя атрибута и значение атрибута.

В регулярном доступе атрибута экземпляр действует как словарь, а имя атрибута действует как ключ в этом вопросе поиска словаря.

экземпляр (атрибут) → значение

В __ slots__ access имя атрибута действует как словарь, и экземпляр действует как ключ в поиске словаря.

атрибут (экземпляр) → значение

В flyweight pattern имя атрибута действует как словарь, и значение действует как ключ в этом словаре, просматривающий экземпляр.

атрибут (значение) → экземпляр

Ответ 8

У вас - по существу - не используется для __slots__.

В то время, когда вы считаете, что вам может понадобиться __slots__, вы действительно хотите использовать шаблоны Легкий или Flyweight. Это случаи, когда вы больше не хотите использовать исключительно объекты Python. Вместо этого вы хотите объект-подобную оболочку Python вокруг массива array, struct или numpy.

class Flyweight(object):

    def get(self, theData, index):
        return theData[index]

    def set(self, theData, index, value):
        theData[index]= value

Класс-подобная оболочка не имеет атрибутов - она ​​просто предоставляет методы, которые действуют на базовые данные. Методы могут быть сведены к методам класса. В самом деле, его можно было бы свести к просто функциям, работающим с базовым массивом данных.

Ответ 9

Очень простой пример атрибута __slot__.

Проблема: Без __slots__

Если у меня нет атрибута __slot__ в моем классе, я могу добавить новые атрибуты к моим объектам.

class Test:
    pass

obj1=Test()
obj2=Test()

print(obj1.__dict__)  #--> {}
obj1.x=12
print(obj1.__dict__)  # --> {'x': 12}
obj1.y=20
print(obj1.__dict__)  # --> {'x': 12, 'y': 20}

obj2.x=99
print(obj2.__dict__)  # --> {'x': 99}

Если вы посмотрите на пример выше, вы увидите, что obj1 и obj2 имеют свои собственные x и y > и python также создал атрибут dict для каждого объекта (obj1 и obj2).

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

Решение: при __slots__

Теперь в следующем примере мой класс Тест содержит атрибут __slots__. Теперь я не могу добавить новые атрибуты к моим объектам (кроме атрибута x), и python больше не создает атрибут dict. Это устраняет накладные расходы для каждого объекта, что может стать значительным, если у вас много объектов.

class Test:
    __slots__=("x")

obj1=Test()
obj2=Test()
obj1.x=12
print(obj1.x)  # --> 12
obj2.x=99
print(obj2.x)  # --> 99

obj1.y=28
print(obj1.y)  # --> AttributeError: 'Test' object has no attribute 'y'

Ответ 10

Другим несколько неясным использованием __slots__ является добавление атрибутов объектного прокси из пакета ProxyTypes, ранее входящего в проект PEAK. Его ObjectWrapper позволяет проксировать другой объект, но перехватывать все взаимодействия с прокси-объектом. Он не очень часто используется (и не поддерживает Python 3), но мы использовали его для реализации поточно-безопасной блокирующей оболочки вокруг реализации async на основе торнадо, которая отбрасывает весь доступ к прокси-объекту через ioloop, используя поточно-безопасную concurrent.Future объекты для синхронизации и возврата результатов.

По умолчанию любой доступ к атрибуту для прокси-объекта даст вам результат от прокси-объекта. Если вам нужно добавить атрибут на прокси-объект, можно использовать __slots__.

from peak.util.proxies import ObjectWrapper

class Original(object):
    def __init__(self):
        self.name = 'The Original'

class ProxyOriginal(ObjectWrapper):

    __slots__ = ['proxy_name']

    def __init__(self, subject, proxy_name):
        # proxy_info attributed added directly to the
        # Original instance, not the ProxyOriginal instance
        self.proxy_info = 'You are proxied by {}'.format(proxy_name)

        # proxy_name added to ProxyOriginal instance, since it is
        # defined in __slots__
        self.proxy_name = proxy_name

        super(ProxyOriginal, self).__init__(subject)

if __name__ == "__main__":
    original = Original()
    proxy = ProxyOriginal(original, 'Proxy Overlord')

    # Both statements print "The Original"
    print "original.name: ", original.name
    print "proxy.name: ", proxy.name

    # Both statements below print 
    # "You are proxied by Proxy Overlord", since the ProxyOriginal
    # __init__ sets it to the original object 
    print "original.proxy_info: ", original.proxy_info
    print "proxy.proxy_info: ", proxy.proxy_info

    # prints "Proxy Overlord"
    print "proxy.proxy_name: ", proxy.proxy_name
    # Raises AttributeError since proxy_name is only set on 
    # the proxy object
    print "original.proxy_name: ", proxy.proxy_name

Ответ 11

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

Вот сравнение создания деревьев объектов с миллионом записей, используя слоты и без слотов. В качестве ссылки также используется производительность при использовании простых dicts для деревьев (Py2.7.10 на OSX):

********** RUN 1 **********
1.96036410332 <class 'css_tree_select.element.Element'>
3.02922606468 <class 'css_tree_select.element.ElementNoSlots'>
2.90828204155 dict
********** RUN 2 **********
1.77050495148 <class 'css_tree_select.element.Element'>
3.10655999184 <class 'css_tree_select.element.ElementNoSlots'>
2.84120798111 dict
********** RUN 3 **********
1.84069895744 <class 'css_tree_select.element.Element'>
3.21540498734 <class 'css_tree_select.element.ElementNoSlots'>
2.59615707397 dict
********** RUN 4 **********
1.75041103363 <class 'css_tree_select.element.Element'>
3.17366290092 <class 'css_tree_select.element.ElementNoSlots'>
2.70941114426 dict

Тест-классы (идентификатор, appart из слотов):

class Element(object):
    __slots__ = ['_typ', 'id', 'parent', 'childs']
    def __init__(self, typ, id, parent=None):
        self._typ = typ
        self.id = id
        self.childs = []
        if parent:
            self.parent = parent
            parent.childs.append(self)

class ElementNoSlots(object): (same, w/o slots)

тестовый код, подробный режим:

na, nb, nc = 100, 100, 100
for i in (1, 2, 3, 4):
    print '*' * 10, 'RUN', i, '*' * 10
    # tree with slot and no slot:
    for cls in Element, ElementNoSlots:
        t1 = time.time()
        root = cls('root', 'root')
        for i in xrange(na):
            ela = cls(typ='a', id=i, parent=root)
            for j in xrange(nb):
                elb = cls(typ='b', id=(i, j), parent=ela)
                for k in xrange(nc):
                    elc = cls(typ='c', id=(i, j, k), parent=elb)
        to =  time.time() - t1
        print to, cls
        del root

    # ref: tree with dicts only:
    t1 = time.time()
    droot = {'childs': []}
    for i in xrange(na):
        ela =  {'typ': 'a', id: i, 'childs': []}
        droot['childs'].append(ela)
        for j in xrange(nb):
            elb =  {'typ': 'b', id: (i, j), 'childs': []}
            ela['childs'].append(elb)
            for k in xrange(nc):
                elc =  {'typ': 'c', id: (i, j, k), 'childs': []}
                elb['childs'].append(elc)
    td = time.time() - t1
    print td, 'dict'
    del droot