Вопрос

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

Это было полезно?

Решение

В 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

Доступ к слотам в Python 3.5 в Ubuntu почти на 30 % быстрее.

>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342

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

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

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

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

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

Атрибуты SQLAlchemy значительная экономия памяти __slots__.

Чтобы убедиться в этом, используйте дистрибутив Anaconda Python 2.7 в Ubuntu Linux с guppy.hpy (он же куча) и 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__ явно, если вам нужна эта функция.

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

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

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'

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

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

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

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

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__ = []"В свой класс.

Другие советы

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

  

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

     

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

     

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

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

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

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

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

Поэтому, если вам нужна структура в стиле C, а не полноценный класс, вы можете использовать <=> для сжатия размера объектов & amp; сокращение времени доступа к атрибутам. Хорошим примером является класс Point, содержащий атрибуты x & Amp; у. Если у вас будет много очков, вы можете попробовать использовать <=> для экономии памяти.

В дополнение к другим ответам, вот пример использования __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']

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

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

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

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

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

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

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

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

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

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

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

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

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

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 также создали атрибут x для каждого объекта ( obj1 и obj2 ).

Предположим, есть ли в моем классе Test тысячи таких объектов? Создание дополнительного атрибута <=> для каждого объекта вызовет много накладных расходов (память, вычислительная мощность и т. Д.) В моем коде.

Решение: с помощью <=>

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

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'

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

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

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

У вас есть & # 8212; по существу & # 8212; бесполезно для __slots__.

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

class Flyweight(object):

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

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

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

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

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

Тестовые классы (идент, апплет из слотов):

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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top