-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathslots.py
More file actions
120 lines (103 loc) · 7.57 KB
/
slots.py
File metadata and controls
120 lines (103 loc) · 7.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
# Copyright (c) 2015 Artur Eganyan
#
# This work is provided "AS IS", WITHOUT ANY WARRANTY, express or implied.
#-------------------------------------------------------------------------------
# Кратко:
# - слоты - это фиксированный набор атрибутов для экземпляров класса
# - слоты задаются переменной __slots__, содержащей имена атрибутов
# - экземпляр не может иметь атрибуты, которые не указаны в __slots__
# - если в __slots__ есть имя "__dict__", экземпляр может иметь любые атрибуты
# - слоты работают быстрее обычных атрибутов, потому что хранятся в массиве, а не в словаре
# Слоты - это фиксированный набор атрибутов для экземпляров класса:
#
# class SomeClass(object):
# __slots__ = <набор атрибутов>
#
# Когда в классе есть __slots__, у его экземпляров будет только указанный
# набор атрибутов и ничего более. У экземпляров не будет и словаря __dict__,
# поэтому им нельзя динамически добавлять атрибуты. <набор атрибутов> должен
# быть контейнером, хранящим строковые имена.
#
# Значения атрибутов располагаются не в словаре экземпляр.__dict__ (которого
# нет), а в массиве, где каждый атрибут имеет свой номер. А слоты, формально -
# это дескрипторы, через которые делается доступ к этому массиву. Как и любой
# дескриптор, слоты хранятся в классе, и при чтении/записи/удалении атрибута
# работают с нужным элементом массива. Например, если __slots__ = ('x', 'y'),
# в классе будут дескрипторы x и y, работающие с первым и вторым элементом
# массива.
#
# Слоты наследуются дочерними классами. __slots__ в дочернем классе обычно
# содержит только новые атрибуты (иначе они будут перекрывать атрибуты
# родительского класса). Но если в дочернем классе не создан хотя бы пустой
# __slots__, у экземпляров не будет ограничений на создание атрибутов (у них
# будет словарь __dict__). И тогда у потомков такого класса __slots__ уже
# ничего не ограничит.
#
# Преимущества слотов:
# - Поскольку набор атрибутов фиксирован, их значения (ссылки на объекты)
# располагаются не в словаре __dict__, а в массиве. Это ускоряет доступ к
# атрибутам и экономит память.
# - Экземпляру нельзя добавить атрибут, которого у него не должно быть.
#
# Замечание: Если в __slots__ добавить имя "__dict__", то экземплярам можно
# будет создавать атрибуты динамически.
class A(object):
__slots__ = ('x', 'y')
a = A()
#print a.x # AttributeError: x. Этот атрибут еще не "создан" в экземпляре
# (хотя, по идее, он уже должен там хранится)
a.x = 1
a.y = 2
#a.z = 3 # AttributeError: 'A' object has no attribute 'z'. Интересно,
# что здесь не такой текст ошибки, как для a.x.
#a.__dict__ # AttributeError: 'A' object has no attribute '__dict__'
print a.x # 1
del a.x # Работает. Возможно, атрибут помечается как удаленный
# (вряд ли он удаляется из массива, это было бы медленно)
#print a.x # AttributeError: x
a.x = 1
print a.x # 1
# Дочерний класс наследует слоты родительского
class B(A):
__slots__ = ('z')
b = B()
b.x = 1
b.y = 2
b.z = 3
#b.h = 4 # AttributeError: 'B' object has no attribute 'h'
# Если у дочернего класса нет хотя бы пустого __slots__, для его экземпляров
# можно будет создавать любые атрибуты
class B(A):
pass
b = B()
b.x = 1 # Это по-прежнему слот от класса A
b.y = 2 # Это тоже
b.t = 3 # А это уже динамически созданный атрибут в словаре b.__dict__
#help(B) # Выведет описание B, где будет написано, что что он
# наследует от A два дескриптора (слота) - x и y
# У потомков класса B тоже не будет ограничений на атрибуты, даже
# если у них есть __slots__
class C(B):
__slots__ = ('z')
c = C()
c.z = 1 # Это, конечно, слот
c.t = 2 # А это динамически созданный атрибут, т.к. экземпляры C
# получают словарь __dict__ от B
# Замечание: Слоты напоминают обычные структуры языка C - в них тоже
# фиксированный набор переменных, и у каждой есть свое смещение внутри
# структуры.
#
# Замечание: Как написано в статье "The Inside Story on New-Style Classes",
# главной причиной создания слотов была производительность. Когда в python
# добавили дескрипторы, инструкция "объект.атрибут = значение" стала приводить
# к проверке, является ли этот атрибут дескриптором. Потому что если является,
# значение надо записывать методом атрибут.__set__, а если нет - по-старому,
# сразу в словарь объект.__dict__. Т.е. сначала делается поиск атрибута в
# цепочке классов, а потом уже значение записывается в объект.__dict__ -
# напрямую или через дескриптор. Возникло опасение, что дополнительный поиск
# снизит производительность, и поэтому были добавлены слоты - чтобы после
# неизбежного первого поиска запись делалась не в словарь __dict__ (что
# приводит к очередному поиску), а в массив. Потом выяснилось, что в этом нет
# необходимости, но убирать слоты было поздно.