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

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

Ответы на вопрос(11)

__slots__.

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

class Flyweight(object):

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

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

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

 oefe24 янв. 2009 г., 23:46
При чем тут Flyweight?__slots__?
 kontulai23 апр. 2013 г., 06:11
Есть ли доступное сравнение между использованием слотов и Flyweight в отношении потребления памяти и скорости?
 S.Lott25 янв. 2009 г., 00:41
@oefe: я конечно не понимаю твой вопрос. Я могу процитировать мой ответ, если это поможет ", когда вы думаете, что вам может понадобитьсяигровые автоматы, вы на самом деле хотите использовать ... Шаблон дизайна Flyweight ". Это то, что имеет отношение к Flyweightигровые автоматы, У вас есть более конкретный вопрос?
 jfs29 нояб. 2009 г., 21:51
@oefe: легкий вес и__slots__ оба метода оптимизации для экономии памяти.__slots__ показывает преимущества, когда у вас много много объектов, а также шаблон проектирования Flyweight. Оба решают одну и ту же проблему.
 Patrick Maupin25 июл. 2015 г., 18:19
Хотя Flyweight, безусловно, полезен в некоторых контекстах, хотите верьте, хотите нет, но ответ «как я могу уменьшить использование памяти в Python при создании объектов zillion» не всегда «не используйте Python для своих объектов zillion». Иногда__slots__ На самом деле это ответ, и, как указывает Евгений, его можно добавить как простую запоздалую мысль (например, сначала вы можете сосредоточиться на правильности, а затем повысить производительность).

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

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

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

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

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

 tzot15 окт. 2011 г., 15:56
Нет, экземпляр класса с__slots__ определеноне как структура в стиле C. Существует словарь на уровне класса, сопоставляющий имена атрибутов с индексами, иначе следующее было бы невозможно:class A(object): __slots__= "value",\n\na=A(); setattr(a, 'value', 1) Я действительно думаю, что этот ответ должен быть разъяснен (я могу сделать это, если хотите). Кроме того, я не уверен, чтоinstance.__hidden_attributes[instance.__class__[attrname]] быстрее чемinstance.__dict__[attrname].

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

экземпляр, имя атрибута и значение атрибута.

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

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

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

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

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

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

 Merlyn Morgan-Graham25 июл. 2014 г., 08:22
Это хорошая доля, и она не очень хорошо вписывается в комментарий к одному из ответов, в котором также предлагаются маховики, но это не полный ответ на сам вопрос. В частности (только в контексте вопроса): почему Flyweight и «в каких случаях следует избегать ...»__slots__?
 Dmitry Rubanovich27 июл. 2014 г., 01:04
@Merlyn Morgan-Graham, он служит подсказкой для выбора: обычный доступ, __slots__ или вес в полете.

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

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

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

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

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

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

 hiwaylon28 нояб. 2011 г., 18:54
Я хотел бы увидеть некоторые подробности о «статической типизации» / точке декоратора, без уничижений. Цитирование отсутствующих третьих лиц бесполезно.__slots__ не решает те же проблемы, что и статическая типизация. Например, в C ++ не ограничивается объявление переменной-члена, а присваивается этой переменной непреднамеренный тип (и принудительно применяется компилятор). Я не одобряю использование__slots__Просто интересуюсь разговором. Спасибо!

__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'

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

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

 Aaron Hall♦02 мая 2016 г., 17:08
В своем ответе я демонстрирую травление щелевого объекта, а также обращаюсь к первой части вашего ответа.
 Rotareti19 июл. 2017 г., 06:28
Я понимаю вашу точку зрения, но слоты также предлагают более быстрый доступ к атрибутам (как заявили другие). В этом случае вам не нужно«создать множество (сотни, тысячи) объектов одного класса» для того, чтобы получить производительность. Вместо этого вам нужномного доступ к одному и тому же (выделенному) атрибуту того же экземпляра. (Пожалуйста, поправьте меня, если я ошибаюсь.)

__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
Решение Вопроса
В Python, какова цель__slots__ а в каких случаях нужно этого избегать?TLDR:

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

Быстрее атрибут доступа.экономия места в памяти.

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

Хранение значений в слотах вместо__dict__.отрицающий__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 (он же хиппи) и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__ явно, если вам нужна эта функция.

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

Встроенный 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__ в родительском, кажется, обеспечивает наибольшую гибкость,позволяя ребенку выбрать, чтобы предотвратить или разрешить (добавлением'__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__ документация (самые последние версии документации 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__, (The__dict__ не инициализируется до тех пор, пока вы его не используете, поэтому вам не нужно беспокоиться о пространстве, занимаемом пустым словарем для каждого создаваемого вами экземпляра.) Если вам не нужно это дополнительное пространство, вы можете добавить фразу "__slots__ = []"к вашему классу.

 NightElfik11 мая 2018 г., 01:53
Этот ответ должен быть частью официальной документации Python о__slots__, Шутки в сторону! Спасибо!
 Aaron Hall♦19 апр. 2018 г., 16:57
@Skandix, спасибо за то, что обратили мое внимание на эту опечатку, оказалось, что это не был экземпляр, я, вероятно, забыл, что редактировал эту часть, когда сохранил ее в истории постов. Это, вероятно, было бы поймано раньше, если бы ясделал правильно и сделал код более копируемым ... Еще раз спасибо!
 Skandix19 апр. 2018 г., 13:34
вау, чертовски ответ - спасибо! Тем не менее, я не поймалclass Child(BaseA, BaseB): __slots__ = ('a', 'b') пример с empy-slots-родители. Почему здесьdictproxy создан вместо поднятияAttributeError заc?
 Aaron Hall♦28 июн. 2018 г., 19:15
@NightElfik, хотите верьте, хотите нет, но я внес свой вклад в документацию по Python__slots__ около года назад:github.com/python/cpython/pull/1819/files

Ваш ответ на вопрос