Слоты, дескрипторы, декораторы, метаклассы
Расширения объектной модели Python
Дескрипторы
Вместо .__dict__ — механизм с getter-ами и setter-ами.
Если в .__dict__ тоже есть такой ключ, полноценный дескриптор «главнее»
Протокол дескриптора — объект с методами .__get__(), .__set__() и .__delete__() (если без __set__(), значит, это не данные, а, скажем, функция)
Это поле класса
⇒ одно на все экземпляры класса
конкретный экземпляр передаётся вторым параметром (None, если вызывается как метод класса), так что при желании можно различить
Имеет преимущество перед полем экземпляра (в отличие от обычных полей класса)
1 class Dsc:
2 value = None
3 def __get__(self, obj, cls):
4 print(f"Get from {cls}:{obj}")
5 return self.value
6
7 def __set__(self, cls, val):
8 print(f"Set {cls} to {val}")
9 self.value = val
10
11 def __delete__(self, cls):
12 print("Delete {cls}")
13 self.value = None
14
15 class C:
16 data = Dsc()
17
18 def __init__(self, name):
19 self.name = name
20
21 def __str__(self):
22 return f"<{self.name}>"
23
24 d = C("De")
25 print(d.data)
26 d.data = 1
27 print(d.data)
28 del d.data
Обратите внимание: дескриптор — поле класса, так что
даст
Set <De> to 100500 Get from <class '__main__.C'>:<De> 100500 Get from <class '__main__.C'>:<Ye> 100500
В методах дескриптора всегда известно, через какой объект идёт доступ, поэтому при желании можно брать id(объект) и на этом основании делать что-то разное.
Если указать только __get__, получится т. н. non-data descriptor, основное отличие которого — возможность «загородить» его полем из __dict__.
Слоты
Про слоты в документации
- Зачем использовать классы/объекты как динамический namespace?
Зачем в каждом объекте есть свой __dict__, если имена полей всех объектов совпадают?
А теперь попробуем:
1 >>> s=slo(2,3)
2 >>> s.readonly
3 100500
4 >>> s.field
5 2
6 >>> s.schmield=4
7 >>> s.schmield
8 4
9 >>> s.foo = 0
10 Traceback (most recent call last):
11 File "<stdin>", line 1, in <module>
12 AttributeError: 'slo' object has no attribute 'foo'
13 >>> s.readonly = 0
14 Traceback (most recent call last):
15 File "<stdin>", line 1, in <module>
16 AttributeError: 'slo' object attribute 'readonly' is read-only
17 >>>
18
Декораторы
Что, если мы хотим «обмазать» все вызовы некоторой функции отладочной информацией?
Неудобно! Поиск с заменой fun(a,b) на dfun(fun,a,b).
Создадим обёрнутую функцию вместо старой:
Всё равно поиск с заменой, хотя и попроще. Тогда просто перебьём имя fun!
Вот это и есть декоратор, записывается так:
Закомментировали @genf — убрали декоратор!
BTW, Запись вида
означает то, что вы подумали: функцию функция(), обмазанную сначала декоратором декоратор1(), а затем — декоратор2().
Параметрические декораторы
Конструкторы декораторов!
- вместо объекта-функции мы пишем вызов этого объекта, значит, возвращать он должен функцию!
вторая часть статьи (+декораторы методов) примеры
∃ декораторы методов в классах. Но это потом.
Метаклассы (одним глазом)
Про метаклассы в документации
- Метакласс — конструктор классов
Никто не запрещает делать конструктор классов так:
Все заметили, что это почти декоратор? Что ему не хватает до того, чтобы быть настоящим декоратором?
В любом случае есть несколько вариантов, когда такой механизм не работает. Есть более правильный способ — метаклассы.
Д/З
TODO