15119
Комментарий:
|
17217
|
Удаления помечены так. | Добавления помечены так. |
Строка 353: | Строка 353: |
* Сначала проверяются параметры в порядке описания в функции, затем вызывается функция, после чего проеряется результат. Ислкючение возникает при первом несовпадении типа. | |
Строка 392: | Строка 393: |
1. Написать функцию,`Stat()` которая умеет выполнять две роли: * С одним параметром `Stat(класс)` — работает как декоратор `класса` * Добавляет в объекты класса сбор статистики по всем полям данных * упомянутым в самом классе (т. е. встречающимся в `vars(класс)`) * имя которых ''не'' начинается с "`_`" * по всем остальным атрибутам (методам, спецметодам и т. п., а так же атрибутам, динамически добавленным в `__init__()`) статистика не ведётся * С двумя параметрами `Stat(объект, "поле")` — выводит статистику использования поля `поле`: два целых числа (количество чтений, количество записей) * `объект` — экземпляр класса, декорированного с помощью `Stat` * `поле` — поле этого класс * `Класс` не является потомком встроенного класса, не переопределяет `__new__()`, `__getattribute__()` и т. п. {{{#!highlight python @Stat class C: A, B = 3, 4 def __init__(self, a=None): if a: self.A = a c, d = C(), C(123) print(Stat(c, "A"), Stat(d, "A")) d.A = c.A * 2 + c.B c.B = d.A - 1 - len([d.B, d.B, d.B]) print(Stat(c, "A"), Stat(c, "B")) print(Stat(d, "A"), Stat(d, "B")) }}} → {{{ (0, 0) (0, 1) (1, 0) (1, 1) (1, 2) (3, 0) }}} |
Слоты, дескрипторы, декораторы
Расширения объектной модели Python
Декораторы
Что, если мы хотим «обмазать» все вызовы некоторой функции отладочной информацией?
Неудобно! Поиск с заменой fun(a,b) на dfun(fun,a,b).
Создадим обёрнутую функцию вместо старой:
Всё равно поиск с заменой, хотя и попроще. Тогда просто перебьём имя fun!
Вот это и есть декоратор, записывается так:
Закомментировали @genf — убрали декоратор!
BTW, Запись вида
означает то, что вы подумали: функцию функция(), обмазанную сначала декоратором декоратор1(), а затем — декоратор2().
Параметрические декораторы
Конструкторы декораторов!
вместо объекта-функции @декоратор мы пишем вызов этого объекта @п_декоратор(параметры), значит, в этом месте произойдёт вызов п_декоратор(параметры), а вот то, что оно вернёт, и послужит декоратором:
вторая часть статьи (+декораторы методов) примеры
Декораторы методов и классов
Методы в классах тоже можно декорировать. И сами классы.
- Декоратор метода — это то же самое, что декоратор функции
Класс — это callable, так что ему ничто не мешает быть декоратором
Однако нужно, чтобы экземпляр класса тоже был callable (иначе как он будет декорировать), так что надо определить метод __call__()
1 class Timer: 2 from time import time 3 from sys import stderr 4 5 def __init__(self, fun): 6 self.function = fun 7 8 def __call__(self, *args, **kwargs): 9 start_time = self.time() 10 result = self.function(*args, **kwargs) 11 end_time = self.time() 12 print(f"Duration: {end_time-start_time} seconds", file=self.stderr) 13 return result 14 15 16 # adding a decorator to the function 17 @Timer 18 def payload(delay): 19 return sorted(sum(range(i)) for i in range(delay)) 20 21 print(payload(10000)[-1])
- Декоратор класса — проще, чем кажется ☺! Это функция, которой передаётся класс, она его жуёт (например, подсовывает или даже перебивает поля), и возвращает новый, пережёванный класc.
- Вариант: честно от него наследуется и возвращает потомка
В частности, functools.total_ordering()
Дескрипторы
подробрая статья (рекомендуется)
Вместо .__dict__ — механизм с getter-ами и setter-ами.
Если в .__dict__ тоже есть такой ключ, полноценный дескриптор «главнее»
Протокол дескриптора — объект с методами .__get__(), .__set__() и .__delete__()
если без __set__(), значит, это не данные, а, скажем, метов (т. н. non-data descriptor)
Это поле класса
⇒ одно на все экземпляры класса
- конкретный экземпляр передаётся вторым параметром
тип (класс) экземпляра передаётся третьим параметром в .__get__()
Например, если пытаться прочесть поле класса класс.дескриптор, второй параметр будет равен None
Если задан .__set__(), Имеет преимущество перед полем экземпляра (в отличие от обычных полей класса)
- если не задан, т. е. для non-data, то, конечно, первое же связывание заведёт на этом месте обычное поле экземпляра
1 class Dsc: 2 def __get__(self, obj, cls): 3 print(f"Get from {cls}:{obj}") 4 return obj._value 5 6 def __set__(self, obj, val): 7 print(f"Set in {obj} to {val}") 8 obj._value = val 9 10 def __delete__(self, obj): 11 print("Delete from {obj}") 12 obj._value = None 13 14 class C: 15 data = Dsc() 16 17 def __init__(self, name): 18 self.name = name 19 20 def __str__(self): 21 return f"<{self.name}>"
- →
Обратите внимание на то, что ._value — это поле конкретного объекта, в которое ходит дескриптор
Слоты
Про слоты в документации
- Зачем использовать классы/объекты как динамический 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
Стандартные декораторы
- →
1 >>> C.fun(1,2,3) 2 Normal: (1, 2, 3) 3 >>> C.cfun(1,2,3) 4 Class: (<class '__main__.C'>, 1, 2, 3) 5 >>> C.sfun(1,2,3) 6 Static: (1, 2, 3) 7 >>> 8 >>> e = C() 9 >>> e.fun(1,2,3) 10 Normal: (<__main__.C object at 0x7f5d72290130>, 1, 2, 3) 11 >>> e.cfun(1,2,3) 12 Class: (<class '__main__.C'>, 1, 2, 3) 13 >>> e.sfun(1,2,3) 14 Static: (1, 2, 3) 15
@property — обёртка вокруг дескриптора
Важное отличие: property — это поле объекта, а не класса, т. е. именно реализация шаблона getter/setter в чистом виде
Обратите внимание на троекратное def x( — не надо придумывать ненужные имена (нельзя, actually ☺)
dataclasses, functools, contextlib…
В частности, @functools.wraps, который помогает сохранить исходное имя и строку документации функции
Д/З
- Прочитать про всё, упомянутое выше. Пощёлкать примеры по каждой теме.
TODO
Написать параметрический декоратор TypeCheck(последовательность_типов, тип_результата), который бросает исключение TypeError при вызове функции со следующим сообщением:
"Type of argument Номер is not Тип", если не совпадает тип позиционного параметра функции и соответствующий ему по порядку тип в последовательности_типов
"Type of argument Имя is not Тип", если не совпадает тип именного параметра функции и соответствующий ему тип в последовательности_типов. Типы именованных параметров перечислены в конце последовательности_типов в порядке их описания в def …
"Type of result is not Тип", если тип возвращённого функцией значения не совпадает с типом_результата
"Function функция must have число arguments" — если количество переданных функции параметров (включая переданные по умолчанию) не соответствует длине последовательности_типов
- Сначала проверяются параметры в порядке описания в функции, затем вызывается функция, после чего проеряется результат. Ислкючение возникает при первом несовпадении типа.
- →
1 >>> valid(3, "--", 10) 2 18 3 >>> valid(3, 7, 10) 4 … 5 TypeError: Type of argument 2 is not <class 'str'> 6 >>> valid(3, "--", "*") 7 … 8 TypeError: Type of argument 3 is not <class 'int'> 9 >>> valid(3, "--", c=1.23) 10 … 11 TypeError: Type of argument 'c' is not <class 'int'> 12 >>> semivalid(2, 2) 13 4 14 >>> semivalid(1, 2) 15 … 16 TypeError: Type of result is not <class 'int'> 17 >>> variable(1,2,3,4) 18 4 19 >>> variable(1,2,a=100, b=500) 20 4 21 >>> variable(1,2,a=100) 22 … 23 TypeError: Function variable must have 4 arguments 24
Написать функцию,Stat() которая умеет выполнять две роли:
С одним параметром Stat(класс) — работает как декоратор класса
- Добавляет в объекты класса сбор статистики по всем полям данных
упомянутым в самом классе (т. е. встречающимся в vars(класс))
имя которых не начинается с "_"
по всем остальным атрибутам (методам, спецметодам и т. п., а так же атрибутам, динамически добавленным в __init__()) статистика не ведётся
- Добавляет в объекты класса сбор статистики по всем полям данных
С двумя параметрами Stat(объект, "поле") — выводит статистику использования поля поле: два целых числа (количество чтений, количество записей)
объект — экземпляр класса, декорированного с помощью Stat
поле — поле этого класс
Класс не является потомком встроенного класса, не переопределяет __new__(), __getattribute__() и т. п.
- →
(0, 0) (0, 1) (1, 0) (1, 1) (1, 2) (3, 0)