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

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

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
867
0
238 242
10

Ответы 10

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

Настоятельно не рекомендуется использовать __slots__ для ограничения создания атрибутов.

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

Некоторые другие функции самоанализа python также могут быть нарушены.

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

Aaron Hall 02.05.2016 18:08

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

Rotareti 19.07.2017 07:28

почему это «крайне не рекомендуется»? Недавно я искал способ ограничить создание динамических атрибутов. Я кое-что нашел, но про слоты не было упоминания. Теперь я читаю о слотах, и мне кажется, что это именно то, что я искал раньше. Что плохого в использовании слотов для предотвращения добавления атрибутов во время выполнения?

largest_prime_is_463035818 17.06.2020 12:08

@ idclev463035818 Я не думаю, что в этом есть что-то неправильное.

Ekrem Dinçel 30.10.2020 22:22

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

The proper use of __slots__ is to save space in objects. Instead of having a dynamic dict that allows adding attributes to objects at anytime, there is a static structure which does not allow additions after creation. [This use of __slots__ eliminates the overhead of one dict for every object.] While this is sometimes a useful optimization, it would be completely unnecessary if the Python interpreter was dynamic enough so that it would only require the dict when there actually were additions to the object.

Unfortunately there is a side effect to slots. They change the behavior of the objects that have slots in a way that can be abused by control freaks and static typing weenies. This is bad, because the control freaks should be abusing the metaclasses and the static typing weenies should be abusing decorators, since in Python, there should be only one obvious way of doing something.

Making CPython smart enough to handle saving space without __slots__ is a major undertaking, which is probably why it is not on the list of changes for P3k (yet).

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

hiwaylon 28.11.2011 21:54

Вы - по сути - не используете __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

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

Какое отношение имеет Легковес к __slots__?

oefe 25.01.2009 01:46

@oefe: Я точно не понимаю твой вопрос. Я могу процитировать свой ответ, если он поможет: «когда вы думаете, что вам может понадобиться слоты, вы действительно хотите использовать ... шаблон проектирования Flyweight». Это то, что Легковес имеет отношение к слоты. У вас есть более конкретный вопрос?

S.Lott 25.01.2009 02:41

@oefe: Flyweight и __slots__ - это методы оптимизации для экономии памяти. __slots__ показывает преимущества, когда у вас много объектов, а также шаблон проектирования Легковес. Оба решают одну и ту же проблему.

jfs 29.11.2009 23:51

Есть ли сравнение между использованием слотов и использованием Flyweight в отношении потребления памяти и скорости?

kontulai 23.04.2013 08:11

... оба метода оптимизации для экономии памяти ... и __slots__ можно внедрить в последнюю очередь.

Evgeni Sergeev 03.06.2015 10:30

Хотя Flyweight, безусловно, полезен в некоторых контекстах, хотите верьте, хотите нет, ответ на вопрос «как я могу уменьшить использование памяти в Python, когда я создаю миллион объектов» не всегда будет «не использовать Python для ваших миллионов объектов». Иногда __slots__ действительно является ответом, и, как указывает Евгений, его можно добавить как простую запоздалую мысль (например, вы можете сначала сосредоточиться на правильности, а затем добавить производительность).

Patrick Maupin 25.07.2015 19:19

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

Nuno André 07.10.2015 18:44

С.Лотт, не могли бы вы пересмотреть или переписать какой-либо из этих ответов?

Aaron Hall 23.01.2016 04:15

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

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

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

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

tzot 15.10.2011 17:56

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

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

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

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

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

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

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

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

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

Это хороший ответ, и он не будет хорошо вписываться в комментарий к одному из ответов, который также предполагает легковесы, но это не полный ответ на сам вопрос. В частности (только в контексте вопроса): почему наилегчайший вес и «каких случаев следует избегать ...» __slots__?

Merlyn Morgan-Graham 25.07.2014 10:22

@Merlyn Morgan-Graham, он служит подсказкой для выбора: регулярный доступ, __slots__ или наилегчайший вес.

Dmitry Rubanovich 27.07.2014 03:04

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

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

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

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

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

Очень простой пример атрибута __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 имеют свои собственные атрибуты Икс и 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__ - это добавление атрибутов к прокси-объекту из пакета 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

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