





Вы можете использовать __slots__, если собираетесь создать множество (сотни, тысячи) объектов одного и того же класса. __slots__ существует только как инструмент оптимизации памяти.
Настоятельно не рекомендуется использовать __slots__ для ограничения создания атрибутов.
Травление объектов с помощью __slots__ не будет работать со стандартным (самым старым) протоколом травления; необходимо указать более позднюю версию.
Некоторые другие функции самоанализа python также могут быть нарушены.
Я понимаю вашу точку зрения, но слоты также предлагают более быстрый доступ к атрибутам (как утверждали другие). В этом случае вам не нужен "создать множество (сотни, тысячи) объектов одного и того же класса" для повышения производительности. Вместо этого вам нужно много доступа к одному и тому же (разделенному) атрибуту того же экземпляра. (Пожалуйста, поправьте меня, если я ошибаюсь.)
почему это «крайне не рекомендуется»? Недавно я искал способ ограничить создание динамических атрибутов. Я кое-что нашел, но про слоты не было упоминания. Теперь я читаю о слотах, и мне кажется, что это именно то, что я искал раньше. Что плохого в использовании слотов для предотвращения добавления атрибутов во время выполнения?
@ idclev463035818 Я не думаю, что в этом есть что-то неправильное.
Цитата Джейкоб Халлен:
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__, просто заинтересован в разговоре. Спасибо!
Вы - по сути - не используете __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: Я точно не понимаю твой вопрос. Я могу процитировать свой ответ, если он поможет: «когда вы думаете, что вам может понадобиться слоты, вы действительно хотите использовать ... шаблон проектирования Flyweight». Это то, что Легковес имеет отношение к слоты. У вас есть более конкретный вопрос?
@oefe: Flyweight и __slots__ - это методы оптимизации для экономии памяти. __slots__ показывает преимущества, когда у вас много объектов, а также шаблон проектирования Легковес. Оба решают одну и ту же проблему.
Есть ли сравнение между использованием слотов и использованием Flyweight в отношении потребления памяти и скорости?
... оба метода оптимизации для экономии памяти ... и __slots__ можно внедрить в последнюю очередь.
Хотя Flyweight, безусловно, полезен в некоторых контекстах, хотите верьте, хотите нет, ответ на вопрос «как я могу уменьшить использование памяти в Python, когда я создаю миллион объектов» не всегда будет «не использовать Python для ваших миллионов объектов». Иногда __slots__ действительно является ответом, и, как указывает Евгений, его можно добавить как простую запоздалую мысль (например, вы можете сначала сосредоточиться на правильности, а затем добавить производительность).
Вы можете сэкономить много памяти с помощью Flyweight, когда вы стремитесь обмениваться атрибутами с одинаковыми значениями между экземплярами, но это может быть не в случае с __slots__.
С.Лотт, не могли бы вы пересмотреть или переписать какой-либо из этих ответов?
Каждый объект 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].
Слоты очень полезны для вызовов библиотеки, чтобы исключить «диспетчеризацию именованных методов» при вызове функций. Это упоминается в SWIG документация. Для высокопроизводительных библиотек, которые хотят уменьшить накладные расходы на функции для часто вызываемых функций, использование слотов намного быстрее.
Теперь это может не иметь прямого отношения к вопросу OP. Это больше связано с построением расширений, чем с использованием синтаксиса слоты для объекта. Но это помогает завершить картину использования слотов и некоторых причин, по которым они используются.
Атрибут экземпляра класса имеет 3 свойства: экземпляр, имя атрибута и значение атрибута.
В доступ к обычным атрибутам экземпляр действует как словарь, а имя атрибута действует как ключ в этом словаре, ищущем значение.
экземпляр (атрибут) -> значение
В __slots__ доступ имя атрибута действует как словарь, а экземпляр действует как ключ в словаре, ищущем значение.
атрибут (экземпляр) -> значение
В модель наилегчайшего веса имя атрибута действует как словарь, а значение действует как ключ в этом словаре, ищущем экземпляр.
атрибут (значение) -> экземпляр
Это хороший ответ, и он не будет хорошо вписываться в комментарий к одному из ответов, который также предполагает легковесы, но это не полный ответ на сам вопрос. В частности (только в контексте вопроса): почему наилегчайший вес и «каких случаев следует избегать ...» __slots__?
@Merlyn Morgan-Graham, он служит подсказкой для выбора: регулярный доступ, __slots__ или наилегчайший вес.
В дополнение к другим ответам вот пример использования __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
В своем ответе я демонстрирую маринование предмета с прорезью, а также обращаюсь к первой части вашего ответа.