Відповіді:
У Python, яка мета
__slots__
і в яких випадках слід цього уникати?
Спеціальний атрибут __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 не заперечує, коли ви помиляєтесь (це, мабуть, повинно бути), проблеми можуть інакше не проявлятися, але ваші об’єкти займуть більше місця, ніж вони повинні. Python 3.8:
>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
Це пояснюється тим, що дескриптор слотів Base має слот, окремий від неправильного. Зазвичай це не може з'являтися, але це може:
>>> w = Wrong()
>>> w.foo = 'foo'
>>> Base.foo.__get__(w)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: foo
>>> Wrong.foo.__get__(w)
'foo'
Найбільший застереження стосується багатократного успадкування - декілька "батьківських класів із порожніми слотами" не можна поєднувати.
Щоб уникнути цього обмеження, дотримуйтесь кращих практик: Вилучіть всі, крім одного або всіх батьків, абстрагування, від яких їх конкретний клас, відповідно, і ваш новий конкретний клас колективно буде успадковано - надання абстракції (-ів) порожні прорізи (як і абстрактні базові класи в стандартна бібліотека).
Дивіться розділ про багаторазове успадкування нижче для прикладу.
Щоб атрибути, імена яких __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__
.
Щоб переконатися в цьому, використовуючи розподіл Анаконди в 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__
завдання з іншим класом, який їх не має (і ви не можете їх додати), якщо макети слотів не однакові. (Мені дуже цікаво дізнатися, хто це робить і чому.)Можливо, вам вдасться вилучити подальші застереження з решти __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>
Тому я б пам’ятав про це, оскільки це вирішена проблема.
Перший абзац - наполовину коротке пояснення, наполовину прогнозний. Ось єдина частина, яка насправді відповідає на питання
Правильне використання -
__slots__
це економія місця в об'єктах. Замість того, щоб мати динамічний дікт, який дозволяє додавати атрибути до об'єктів у будь-який час, існує статична структура, яка не дозволяє додавати після створення. Це дозволяє економити накладні витрати одного зображення на кожен об'єкт, який використовує слоти
Друга половина бажана думка, і поза межею:
Хоча це іноді корисна оптимізація, було б абсолютно непотрібно, якби інтерпретатор Python був досить динамічним, щоб вимагати дікта лише тоді, коли насправді були доповнення до об'єкта.
Python насправді робить щось подібне до цього, створюючи лише той __dict__
час, коли доступ до нього, але створення безлічі об'єктів без даних є досить смішним.
Другий абзац спрощує та пропускає фактичні причини, яких слід уникати __slots__
. Нижче не є реальною причиною уникати слотів (про фактичні причини див. Решту моєї відповіді вище.):
Вони змінюють поведінку об'єктів, які мають слоти, таким чином, щоб їх можна було зловживати керуючими виродками та статичним набором вихідних.
Потім він обговорює інші способи досягнення цієї перекрученої мети з Python, не обговорюючи нічого спільного __slots__
.
Третій параграф - більш бажане мислення. Разом це, головним чином, непримітний вміст, який відповідач навіть не написав, і сприяє боєприпасам для критиків сайту.
Створіть кілька звичайних об'єктів та об'єктів з прорізом:
>>> class Foo(object): pass
>>> class Bar(object): __slots__ = ()
Миттєвий мільйон з них:
>>> foos = [Foo() for f in xrange(1000000)]
>>> bars = [Bar() for b in xrange(1000000)]
Огляньте guppy.hpy().heap()
:
>>> guppy.hpy().heap()
Partition of a set of 2028259 objects. Total size = 99763360 bytes.
Index Count % Size % Cumulative % Kind (class / dict of class)
0 1000000 49 64000000 64 64000000 64 __main__.Foo
1 169 0 16281480 16 80281480 80 list
2 1000000 49 16000000 16 96281480 97 __main__.Bar
3 12284 1 987472 1 97268952 97 str
...
Доступ до звичайних об'єктів та їх __dict__
та огляньте ще раз:
>>> for f in foos:
... f.__dict__
>>> guppy.hpy().heap()
Partition of a set of 3028258 objects. Total size = 379763480 bytes.
Index Count % Size % Cumulative % Kind (class / dict of class)
0 1000000 33 280000000 74 280000000 74 dict of __main__.Foo
1 1000000 33 64000000 17 344000000 91 __main__.Foo
2 169 0 16281480 4 360281480 95 list
3 1000000 33 16000000 4 376281480 99 __main__.Bar
4 12284 0 987472 0 377268952 99 str
...
Це узгоджується з історією Python, починаючи з об'єднати типи та класи в Python 2.2
Якщо ви підклас вбудованого типу, додатковий простір автоматично додається до примірників
__dict__
та__weakrefs__
. (__dict__
Ініціалізація не ініціалізована, поки ви не використовуєте її, тому вам не слід турбуватися про простір, який займає порожній словник для кожного створеного вами екземпляра.) Якщо вам не потрібно це додаткове місце, ви можете додати фразу "__slots__ = []
" в ваш клас.
__slots__
. Серйозно! Дякую!
__slots__
приблизно рік тому: github.com/python/cpython/pull/1819/files
Цитуючи Якова Халлена :
Правильне використання -
__slots__
це економія місця в об'єктах. Замість того, щоб мати динамічний дікт, який дозволяє додавати атрибути до об’єктів у будь-який час, існує статична структура, яка не дозволяє додавати після створення. [Це використання__slots__
виключає накладні витрати одного диктату для кожного об'єкта.] Хоча це іноді корисна оптимізація, було б абсолютно непотрібно, якби інтерпретатор Python був досить динамічним, щоб вимагати дікта лише тоді, коли насправді були доповнення до об’єкт.На жаль, є побічний ефект для слотів. Вони змінюють поведінку об'єктів, які мають слоти, таким чином, щоб їх можна було зловживати керуючими виродками та статичним набором вихідних. Це погано, тому що контрольні виродки повинні зловживати метакласами, а статичні друкарські вихідні повинні зловживати декораторами, оскільки в Python має бути лише один очевидний спосіб робити щось.
Зробити CPython достатньо розумним для обробки економії місця без
__slots__
- це головне завдання, ймовірно, тому він ще не в списку змін для P3k (поки що).
__slots__
не стосується тих самих проблем, що й статичне введення тексту. Наприклад, у C ++ не декларування змінної-члена обмежується, це присвоєння невмисному типу (і примусовому компілятору) цій змінній. Я не потураю використанню __slots__
, просто зацікавлений у розмові. Дякую!
Ви б хотіли використовувати, __slots__
якщо збираєтесь інстанціювати багато (сотні, тисячі) об'єктів одного класу. __slots__
існує лише як засіб оптимізації пам'яті.
Вкрай не рекомендується використовувати __slots__
для обмеження створення атрибутів.
Перебирання об'єктів, __slots__
які не працюватимуть із протоколом соління за замовчуванням (найстарший) необхідно вказати більш пізню версію.
Деякі інші особливості самоаналізу пітона також можуть негативно впливати.
Кожен об’єкт python має __dict__
attribute - словник, що містить усі інші атрибути. наприклад, коли ви вводите self.attr
python насправді робить 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]
.
Окрім інших відповідей, ось приклад використання __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 разів за рахунок необхідності ввести спеціальний код підбору, якщо і коли це стане необхідним.
Слоти дуже корисні для викликів бібліотеки для усунення "названого методу відправки" під час здійснення функціональних дзвінків. Про це йдеться в документації SWIG . Для бібліотек з високою продуктивністю, які хочуть зменшити накладні витрати для часто званих функцій, використовуючи слоти, це набагато швидше.
Зараз це може не бути безпосередньо пов'язане з питанням про ОП. Це пов’язано більше зі створенням розширень, ніж із використанням синтаксису слотів на об’єкті. Але це допомагає доповнити картину для використання слотів та деяких міркувань за ними.
Атрибут екземпляра класу має 3 властивості: екземпляр, ім'я атрибута та значення атрибута.
У звичайному доступі до атрибутів екземпляр діє як словник, а ім'я атрибута виступає в якості ключа в словнику, що шукає значення.
instance (attribute) -> значення
У доступі __slots__ назва атрибута виступає як словник, а екземпляр виступає ключем у словнику, що шукає значення.
атрибут (екземпляр) -> значення
У малюнку легкої ваги назва атрибута виступає як словник, а значення виступає ключовим у словнику, шукаючи екземпляр.
атрибут (значення) -> екземпляр
__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__
- додавання атрибутів до об’єктного проксі з пакету 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
У вас - по суті, немає жодної користі __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__
?
__slots__
є обома методами оптимізації для збереження пам'яті. __slots__
показує переваги, коли у вас багато багатьох об'єктів, а також модель Flyweight Design. Обидва вирішують ту саму проблему.
__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
Тестові класи (ident, appart від слотів):
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
class Child(BaseA, BaseB): __slots__ = ('a', 'b')
приклад із батьками-еміті-слотами. Чому тутdictproxy
створено замість піднявшиAttributeError
дляc
?