Слоты, дескрипторы, декораторы

Расширения объектной модели Python

Декораторы

Что, если мы хотим «обмазать» все вызовы некоторой функции отладочной информацией?

   1 def fun(a,b):
   2     return a*2+b
   3 
   4 def dfun(f, *args):
   5     print(">", *args)
   6     res = f(*args)
   7     print("<", res)
   8     return res
   9 
  10 
  11 print(fun(2,3))
  12 print(dfun(fun,2,3))

Неудобно! Поиск с заменой fun(a,b) на dfun(fun,a,b).

Создадим обёрнутую функцию вместо старой:

   1 # ...
   2 def genf(f):
   3     def newfun(*args):
   4         print(">", *args)
   5         res = f(*args)
   6         print("<", res)
   7         return res
   8     return newfun
   9 
  10 newf = genf(fun)
  11 print(newf(2,3))

Всё равно поиск с заменой, хотя и попроще. Тогда просто перебьём имя fun!

   1 # ...
   2 fun = genf(fun)
   3 print(fun(2,3))

Вот это и есть декоратор, записывается так:

   1 def genf(f):
   2     def newfun(*args):
   3         print(">", *args)
   4         res = f(*args)
   5         print("<", res)
   6         return res
   7     return newfun
   8 
   9 @genf
  10 def fun(a,b):
  11     return a*2+b
  12 
  13 print(fun(2,3))

Закомментировали @genf — убрали декоратор!

статья на хабре

BTW, Запись вида

   1 @декоратор2
   2 @декоратор1
   3 def функция(…)
   4 

означает то, что вы подумали: функцию функция(), обмазанную сначала декоратором декоратор1(), а затем — декоратор2().

Параметрические декораторы

Конструкторы декораторов!

вторая часть статьи (+декораторы методов) примеры

Декораторы методов и классов

Методы в классах тоже можно декорировать. И сами классы.

Дескрипторы

Вместо .__dict__ — механизм с getter-ами и setter-ами.

Слоты

   1 class slo:
   2 
   3     __slots__ = ["field", "schmield"]
   4     readonly = 100500
   5 
   6     def __init__(self, f, s):
   7         self.field, self.schmield = f, s

А теперь попробуем:

   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. Прочитать про всё, упомянутое выше. Пощёлкать примеры по каждой теме.

TODO

  1. EJudge: TypeCheck 'Проверка типов'

    Написать параметрический декоратор TypeCheck(последовательность_типов, тип_результата), который бросает исключение TypeError при вызове функции со следующим сообщением:

    • "Type of argument Номер is not Тип", если не совпадает тип позиционного параметра функции и соответствующий ему по порядку тип в последовательности_типов

    • "Type of argument 'Имя' is not Тип", если не совпадает тип именного параметра функции и соответствующий ему тип в последовательности_типов.

      • Типы именованных параметров перечислены в конце последовательности_типов

      • Типы именованных параметров проверяются в порядке их появления при вызове функции
    • "Type of result is not Тип", если тип возвращённого функцией значения не совпадает с типом_результата

    • "Function функция must have число arguments" — если количество переданных функции параметров (включая переданные по умолчанию) не соответствует длине последовательности_типов

    • Сначала проверяются параметры в порядке описания в функции, затем вызывается функция, после чего проеряется результат. Ислкючение возникает при первом несовпадении типа.
    Input:

       1 @TypeCheck((int, str, int), int)
       2 def valid(a, b, c=0):
       3     return len(b*(a+1))+c
       4 
       5 print(valid(3, "--", 10))
       6 try:
       7     valid(3, 7, 10)
       8 except TypeError as E:
       9     print(E)
    
    Output:

    18
    Type of argument 2 is not <class 'str'>
  2. EJudge: FieldCounter 'Статистика с полей'

    Написать функцию,Stat() которая умеет выполнять две роли:

    • С одним параметром Stat(класс) — работает как декоратор класса

      • Добавляет в объекты класса сбор статистики по всем полям данных
        • упомянутым в самом классе (т. е. встречающимся в vars(класс))

        • имя которых не начинается с "_"

      • По всем остальным атрибутам (методам, спецметодам и т. п., а так же атрибутам, динамически добавленным в __init__() и других методах) статистика не ведётся

    • С двумя параметрами Stat(объект, "поле") — выводит статистику использования поля поле: два целых числа (количество чтений, количество записей)

      • объект — экземпляр класса, декорированного с помощью Stat

      • поле — поле этого класса

      • Если статистика по данному полю не ведётся, или поля не существует, обя значения равны 0
    • Экземпляр класса инициализируется с помощью __init__(), т. е. класс не является потомком встроенного класса, не переопределяет __new__(), __getattribute__() и т. п., изначально не содержит слотов/дескрипторов.

    Input:

       1 @Stat
       2 class C:
       3     A, B = 3, 4
       4     def __init__(self, a=None):
       5         if a:
       6             self.A = a
       7 
       8 c, d = C(), C(123)
       9 print(Stat(c, "A"), Stat(d, "A"))
      10 d.A = c.A * 2 + c.B
      11 c.B = d.A - 1 - len([d.B, d.B, d.B])
      12 print(Stat(c, "A"), Stat(c, "B"))
      13 print(Stat(d, "A"), Stat(d, "B"))
      14 print(Stat(c, "Foo"))
    
    Output:

    (0, 0) (0, 1)
    (1, 0) (1, 1)
    (1, 2) (3, 0)
    (0, 0)

LecturesCMC/PythonIntro2020/11_MiscOOP (last edited 2020-11-21 17:30:16 by FrBrGeorge)