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

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

627 голосов
/ 23 января 2009

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

Ответы [ 11 ]

754 голосов
/ 21 января 2015

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

TLDR:

Специальный атрибут __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

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

>>> 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__. Еще раз спасибо Python за ленивую оценку, __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__, вы должны создать подкласс 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__' к __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__' в слотах мы теряем некоторые преимущества в размере с преимуществом наличия динамического назначения и все еще наличия слотов для имен, которые мы ожидаем.

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

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

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

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

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

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__ в родительском элементе, по-видимому, обеспечивает наибольшую гибкость, позволяя ребенку выбрать, чтобы запретить или разрешить (добавив '__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'

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

Выход здесь на конечности : Если вы сочиняете mixins или используете абстрактные базовые классы , которые не предназначены для реализации, пустое значение __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__ (самые последние документы по 3.7 для разработчиков) , к которой я недавно внес значительный вклад.

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

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

Не «используйте __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__ = []" для вашего класса.

261 голосов
/ 23 января 2009

Цитата Джейкоб Халлен :

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

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

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

120 голосов
/ 23 января 2009

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

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

58 голосов
/ 23 января 2009

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

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

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

18 голосов
/ 03 июня 2015

В дополнение к другим ответам, вот пример использования __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 раз , за счет необходимости писать собственный код выбора, если и когда это станет необходимым.

11 голосов
/ 25 ноября 2012

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

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

6 голосов
/ 05 июня 2014

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

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

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

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

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

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

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

2 голосов
/ 07 февраля 2017

Другое неясное использование __slots__ - это добавление атрибутов в прокси-объект объекта из пакета ProxyTypes, который ранее был частью проекта PEAK. Его ObjectWrapper позволяет вам прокси-объект другого объекта, но перехватывать все взаимодействия с прокси-объектом. Он не очень широко используется (и не поддерживает Python 3), но мы использовали его для реализации поточно-ориентированной блокирующей оболочки вокруг асинхронной реализации, основанной на торнадо, которая перенаправляет весь доступ к проксируемому объекту через 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
2 голосов
/ 02 ноября 2016

Очень простой пример атрибута __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__

Теперь в следующем примере мой класс Test содержит атрибут __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'
2 голосов
/ 23 января 2009

Вы - по существу - бесполезны для __slots__.

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

class Flyweight(object):

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

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

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

...