7546
Комментарий:
|
14905
|
Удаления помечены так. | Добавления помечены так. |
Строка 1: | Строка 1: |
= Слоты, дескрипторы, декораторы, метаклассы = | = Слоты, дескрипторы, декораторы = |
Строка 3: | Строка 3: |
== Декораторы == Что, если мы хотим «обмазать» все вызовы некоторой функции отладочной информацией? {{{#!python def fun(a,b): return a*2+b def dfun(f, *args): print(">", *args) res = f(*args) print("<", res) return res print(fun(2,3)) print(dfun(fun,2,3)) }}} Неудобно! Поиск с заменой `fun(a,b)` на `dfun(fun,a,b)`. Создадим обёрнутую функцию `вместо` старой: {{{#!python # ... def genf(f): def newfun(*args): print(">", *args) res = f(*args) print("<", res) return res return newfun newf = genf(fun) print(newf(2,3)) }}} Всё равно поиск с заменой, хотя и попроще. Тогда просто перебьём имя `fun`! {{{#!python # ... fun = genf(fun) print(fun(2,3)) }}} Вот это и есть декоратор, записывается так: {{{#!python def genf(f): def newfun(*args): print(">", *args) res = f(*args) print("<", res) return res return newfun @genf def fun(a,b): return a*2+b print(fun(2,3)) }}} Закомментировали `@genf` — убрали декоратор! [[https://habrahabr.ru/post/141411/|статья на хабре]] BTW, Запись вида {{{#!python @декоратор2 @декоратор1 def функция(…) … }}} означает то, что вы подумали: функцию `функция()`, обмазанную сначала декоратором `декоратор1()`, а затем — `декоратор2()`. === Параметрические декораторы === Конструкторы декораторов! * вместо объекта-функции `@декоратор` мы пишем вызов этого объекта `@п_декоратор(параметры)`, значит, в этом месте произойдёт ''вызов'' `п_декоратор(параметры)`, а вот то, ''что оно вернёт'', и послужит декоратором: {{{#!highlight python def multicall(times): def decorator(fun): def newfun(*args): return [fun(*args) for i in range(times)] return newfun return decorator @multicall(5) def simplefun(N): return N*2+1 print(*simplefun(4)) }}} [[https://habrahabr.ru/post/141501/|вторая часть статьи (+декораторы методов)]] [[https://www.learnpython.org/en/Decorators|примеры]] === Декораторы методов и классов === Методы в классах тоже можно декорировать. И сами классы. * Декоратор метода — это то же самое, что декоратор функции * Класс — это callable, так что ему [[https://www.geeksforgeeks.org/class-as-decorator-in-python/|ничто не мешает быть декоратором]] * Однако нужно, чтобы ''экземпляр'' класса тоже был callable (иначе как он будет декорировать), так что надо определить метод `__call__()` {{{#!highlight python class Timer: from time import time from sys import stderr def __init__(self, fun): self.function = fun def __call__(self, *args, **kwargs): start_time = self.time() result = self.function(*args, **kwargs) end_time = self.time() print(f"Duration: {end_time-start_time} seconds", file=self.stderr) return result # adding a decorator to the function @Timer def payload(delay): return sorted(sum(range(i)) for i in range(delay)) print(payload(10000)[-1]) }}} * Декоратор класса — проще, чем кажется ☺! Это функция, которой передаётся класс, она его жуёт (например, подсовывает или даже перебивает поля), и возвращает новый, пережёванный класc. * [[https://www.kite.com/python/answers/how-to-decorate-a-class-in-python|пример]] * Вариант: честно от него наследуется и возвращает потомка * В частности, [[py3doc:functools#functools.total_ordering|functools.total_ordering()]] |
|
Строка 6: | Строка 132: |
* [[py3how:descriptor.html|подробрая статья]] | * [[py3how:descriptor.html|подробрая статья]] (рекомендуется) |
Строка 11: | Строка 137: |
* ''Протокол дескриптора'' — объект с методами `.__get__()`, `.__set__()` и `.__delete__()` (если без `__set__()`, значит, это не данные, а, скажем, функция) | * ''Протокол дескриптора'' — объект с методами `.__get__()`, `.__set__()` и `.__delete__()` * если без `__set__()`, значит, это не данные, а, скажем, метов (т. н. ''non-data descriptor'') |
Строка 14: | Строка 141: |
* конкретный экземпляр передаётся вторым параметром (`None`, если вызывается как метод класса), так что при желании можно различить * Имеет ''преимущество'' перед полем экземпляра (в отличие от обычных полей класса) {{{#!highlight python3 |
* конкретный экземпляр передаётся вторым параметром * ''тип'' (класс) экземпляра передаётся третьим параметром в `.__get__()` * Например, если пытаться прочесть поле класса `класс.дескриптор`, второй параметр будет равен `None` * Если задан `.__set__()`, Имеет ''преимущество'' перед полем экземпляра (в отличие от обычных полей класса) * если не задан, т. е. для non-data, то, конечно, первое же связывание заведёт на этом месте обычное поле экземпляра {{{#!highlight python |
Строка 18: | Строка 148: |
value = None | |
Строка 21: | Строка 150: |
return self.value def __set__(self, cls, val): print(f"Set {cls} to {val}") self.value = val def __delete__(self, cls): print("Delete {cls}") self.value = None |
return obj._value def __set__(self, obj, val): print(f"Set in {obj} to {val}") obj._value = val def __delete__(self, obj): print("Delete from {obj}") obj._value = None |
Строка 39: | Строка 168: |
d = C("De") print(d.data) d.data = 1 print(d.data) del d.data }}} Обратите внимание: дескриптор — поле ''класса'', так что {{{#!highlight python3 e = C("Ye") d.data = 100500 print(d.data) print(e.data) }}} даст {{{ Set <De> to 100500 Get from <class '__main__.C'>:<De> |
}}} → {{{#!highlight pycon >>> c = C("Obj") >>> c.data = 100500 Set in <Obj> to 100500 >>> c.data Get from <class '__main__.C'>:<Obj> |
Строка 59: | Строка 177: |
Get from <class '__main__.C'>:<Ye> 100500 }}} В методах дескриптора всегда известно, через какой объект идёт доступ, поэтому при желании можно брать `id(объект)` и на этом основании делать что-то разное. Если указать только `__get__`, получится т. н. non-data descriptor, основное отличие которого — возможность «загородить» его полем из `__dict__`. |
>>> del c.data Delete from {obj} >>> print(c.data) Get from <class '__main__.C'>:<Obj> None }}} * Обратите внимание на то, что `._value` — это поле ''конкретного объекта'', в которое ходит дескриптор |
Строка 71: | Строка 190: |
* Зачем в ''каждом'' объекте есть ''свой'' `__dict__`, если ''имена'' полей всех объектов совпадают? | * Зачем в ''каждом'' объекте есть ''свой'' `__dict__`, если имена полей всех объектов ''обычно'' совпадают? |
Строка 104: | Строка 223: |
== Декораторы == Что, если мы хотим «обмазать» все вызовы некоторой функции отладочной информацией? {{{#!python def fun(a,b): return a*2+b def dfun(f, *args): print(">", *args) res = f(*args) print("<", res) return res print(fun(2,3)) print(dfun(fun,2,3)) }}} Неудобно! Поиск с заменой `fun(a,b)` на `dfun(fun,a,b)`. Создадим обёрнутую функцию `вместо` старой: {{{#!python # ... def genf(f): def newfun(*args): print(">", *args) res = f(*args) print("<", res) return res return newfun newf = genf(fun) print(newf(2,3)) }}} Всё равно поиск с заменой, хотя и попроще. Тогда просто перебьём имя `fun`! {{{#!python # ... fun = genf(fun) print(fun(2,3)) }}} Вот это и есть декоратор, записывается так: {{{#!python def genf(f): def newfun(*args): print(">", *args) res = f(*args) print("<", res) return res return newfun @genf def fun(a,b): return a*2+b print(fun(2,3)) }}} Закомментировали `@genf` — убрали декоратор! [[https://habrahabr.ru/post/141411/|статья на хабре]] BTW, Запись вида {{{#!python @декоратор2 @декоратор1 def функция(…) … }}} означает то, что вы подумали: функцию `функция()`, обмазанную сначала декоратором `декоратор1()`, а затем — `декоратор2()`. === Параметрические декораторы === Конструкторы декораторов! * вместо объекта-функции мы пишем вызов этого объекта, значит, возвращать он должен функцию! [[https://habrahabr.ru/post/141501/|вторая часть статьи (+декораторы методов)]] [[https://www.learnpython.org/en/Decorators|примеры]] ∃ декораторы методов в классах. Но это потом. == Метаклассы (одним глазом) == * Про метаклассы [[py3ref:datamodel.html#metaclasses|в документации]] * [[https://realpython.com/python-metaclasses/|На realpython.com]] * Метакласс — конструктор классов * <!> Никто не запрещает делать конструктор классов так: {{{#!python def fc(st): class _bzz: def __init__(self): self.st = st return _bzz C = fc(10500) c = C() print(c.st) }}} Все заметили, что это почти декоратор? Что ему не хватает до того, чтобы быть настоящим декоратором? В любом случае есть несколько вариантов, когда такой механизм не работает. Есть более правильный способ — метаклассы. {{{#!python from time import ctime class D(type): def __str__(self): self.__date = ctime() return f"<class {self.__class__.__name__} created at {self.__date}>" class C(metaclass=D): pass class E: pass }}} {{{#!highlight pycon >>> c, e = C(), E() >>> print(C, E) <class D created at Sun Dec 1 18:17:59 2019> <class '__main__.E'> }}} |
== Стандартные декораторы == * [[py3doc:functions#classmethod|@classmethod]] и [[py3doc:functions#staticmethod|@staticmethod]]: {{{#!highlight python class C: def fun(*args): print("Normal:", args) @classmethod def cfun(*args): print("Class:", args) @staticmethod def sfun(*args): print("Static:", args) }}} → {{{#!highlight pycon >>> C.fun(1,2,3) Normal: (1, 2, 3) >>> C.cfun(1,2,3) Class: (<class '__main__.C'>, 1, 2, 3) >>> C.sfun(1,2,3) Static: (1, 2, 3) >>> >>> e = C() >>> e.fun(1,2,3) Normal: (<__main__.C object at 0x7f5d72290130>, 1, 2, 3) >>> e.cfun(1,2,3) Class: (<class '__main__.C'>, 1, 2, 3) >>> e.sfun(1,2,3) Static: (1, 2, 3) }}} * [[py3doc:functions#property|@property]] — обёртка вокруг дескриптора * (!) Важное отличие: `property` — это поле `объекта`, а не класса, т. е. именно реализация шаблона [[RW:Геттер_(программирование)|getter/setter]] в чистом виде {{{#!highlight python class C: def __init__(self): self._var = None @property def x(self): """I'm the 'x' property.""" return self._var @x.setter def x(self, value): self._var = value @x.deleter def x(self): del self._var }}} * Обратите внимание на троекратное `def x(` — не надо придумывать ненужные имена (''нельзя'', actually ☺) * [[py3doc:dataclasses]], [[py3doc:functools]], [[py3doc:contextlib]]… ##'''Вот отсюда точно не успеем''' ##== Метаклассы (одним глазом) == ## * Про метаклассы [[py3ref:datamodel.html#metaclasses|в документации]] ## * [[https://realpython.com/python-metaclasses/|На realpython.com]] ## * Метакласс — конструктор классов ## * <!> Никто не запрещает делать конструктор классов так: ##{{{#!python ##def fc(st): ## class _bzz: ## def __init__(self): ## self.st = st ## return _bzz ##C = fc(10500) ##c = C() ##print(c.st) ##}}} ##Все заметили, что это почти декоратор? Что ему не хватает до того, чтобы быть настоящим декоратором? ##В любом случае есть несколько вариантов, когда такой механизм не работает. Есть более правильный способ — метаклассы. ## * Создание класса с помощью [[py3doc:functions.html#type|type(name, bases, dict)]] ## {{{#!python ## class C: ## pass ## }}} ## это ## {{{#!python ## C = type("C", (), {}) ## }}} ## * `type` — это просто класс такой ⇒ ## {{{#!python ## class overtype(type): ## pass ## T = overtype("Boo", (), {}) ## }}} ## * ну так вот, «`T = overtype(…)`» == «`class T(metaclass=overtype): …`» ## * определённые правила для [[py3ref:datamodel.html#object.__init__|__init__()]] и [[py3ref:datamodel.html#object.__new__|__new__()]] ##<!> Не путать с наследованием! ##{{{#!python ##from time import ctime ##class D(type): ## def __str__(self): ## self.__date = ctime() ## return f"<class {self.__class__.__name__} created at {self.__date}>" ##class C(metaclass=D): ## pass ##class E: ## pass ##}}} ##{{{#!highlight pycon ##>>> c, e = C(), E() ##>>> print(C, E) ##<class D created at Sun Dec 1 18:17:59 2019> <class '__main__.E'> ##}}} ##'''TODO''' про `.__new_()` подробнее |
Строка 226: | Строка 345: |
1.#0 Прочитать про всё, упомянутое выше. Пощёлкать примеры по каждой теме. | |
Строка 227: | Строка 347: |
1. Написать параметрический декоратор `TypeCheck(последовательность_типов, тип_результата)`, который бросает исключение `TypeError` при вызове функции со следующим сообщением: * "`Type of argument Номер is not Тип`", если не совпадает тип позиционного параметра функции и соответствующий ему по порядку тип в `последовательности_типов` * "`Type of argument Имя is not Тип`", если не совпадает тип именного параметра функции и соответствующий ему тип в `последовательности_типов`. Типы именованных параметров перечислены в конце `последовательности_типов` в порядке их описания в `def …` * "`Type of result is not Тип`", если тип возвращённого функцией значения не совпадает с `типом_результата` * "`Function функция must have число arguments`" — если количество переданных функции параметров (включая переданные по умолчанию) не соответствует длине `последовательности_типов` {{{#!highlight python @TypeCheck((int, str, int), int) def valid(a, b, c=0): return len(b*(a+1))+c @TypeCheck([int, int], int) def semivalid(a, b): return a/b if a%2 else a*b @TypeCheck((int for i in range(4)), int) def variable(*args, **kwargs): return len(args)+len(kwargs) }}} → {{{#!highlight pycon >>> valid(3, "--", 10) 18 >>> valid(3, 7, 10) … TypeError: Type of argument 2 is not <class 'str'> >>> valid(3, "--", "*") … TypeError: Type of argument 3 is not <class 'int'> >>> valid(3, "--", c=1.23) … TypeError: Type of argument 'c' is not <class 'int'> >>> semivalid(2, 2) 4 >>> semivalid(1, 2) … TypeError: Type of result is not <class 'int'> >>> variable(1,2,3,4) 4 >>> variable(1,2,a=100, b=500) 4 >>> variable(1,2,a=100) … TypeError: Function variable must have 4 arguments }}} |
Слоты, дескрипторы, декораторы
Расширения объектной модели 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 ☺)
Д/З
- Прочитать про всё, упомянутое выше. Пощёлкать примеры по каждой теме.
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