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

Расширения объектной модели 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().

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

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

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

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

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

Дескрипторы

Механизм getter/setter

Слоты

Недостатки реализации объектной модели в Python с помощью __dict__:

Слоты:

   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 >>> slo.field
  18 <member 'field' of 'slo' objects>
  19 >>> type(slo.field)
  20 <class 'member_descriptor'>
  21 

Стандартные декораторы

Д/З

  1. Прочитать про всё, упомянутое выше. Пощёлкать примеры по каждой теме.
  2. EJudge: TypeCast 'Приведение типов'

    Написать параметрический декоратор cast(тип), который пытается преобразовать результат декорируемой функции к заданному типу. Исключения проверять не надо, но надо пользоваться @wraps.

    Input:

    @cast(int)
    def fun(a, b):
        return a * 2 + b
    print(fun(12, 34) * 2)
    print(fun("12", "34") * 2)
    print(fun(12.765, 34.654) * 2)
    Output:

    116
    242468
    120
  3. EJudge: UniSize 'Унисайз'

    Написать декоратор класса под названием sizer, который будет добавлять в класс поле size. При обращении к этому полю возвращается len() объекта, если объёкт имеет длину, иначе же abs() объекта, если от него вычисляется модуль, и 0 в противном случае.

    Input:

       1 @sizer
       2 class S(str):
       3     pass
       4 
       5 @sizer
       6 class N(complex):
       7     pass
       8 
       9 @sizer
      10 class E(Exception):
      11     pass
      12 
      13 for obj in S("QWER"), N(3+4j), E("Exceptions know no lengths!"):
      14     print(obj, obj.size)
    
    Output:

    QWER 4
    (3+4j) 5.0
    Exceptions know no lengths! 0
  4. EJudge: FuncCount 'Счётчик вызовов'

    Написать декоратор counter, который заводит внутри объекта-функции метод counter(). Этот метод возвращает, сколько раз эта функция была вызвана. Использовать @wraps. Дополнительное требование: никаких других глобальных объектов (кроме counter и wraps).

    Input:

       1 @counter
       2 def fun(a, b):
       3     return a * 1 + b
       4 
       5 print(fun.counter())
       6 res = sum(fun(i, i + 1) for i in range(5))
       7 print(fun.counter(), res)
    
    Output:

    0
    5 25
  5. EJudge: SemClass 'Семафор'

    Написать класс Lock, который реализует абстракцию «двоичный семафор», а также декоратор класса @Lock.locked, который добавляет поле-семафор .lock в класс. Протокол работы семафора:

    1. obj.lock = "имя" — задаём имя семафора, который собираемся захватывать

      • Если при этом какой-то семафор был уже захвачен (в том числе с тем же именем), он освобождается
    2. obj.lock — атомарная операция проверки доступности и одновременного захвата семафора. Совпадает с именем семафора, если его захватить удалось, если нет — равна None.

      • Если семафор уже захвачен именно этим lock-ом, результат — имя семафора

      • Если семафор не задан, результат — None

    3. del obj.lock — освобождение семафора, если он захвачен именно этим lock-ом

      • В противном случае не происходит ничего
    4. При удалении объекта, содержащего lock (например, в результате уменьшения счётчика ссылок до 0), захваченный семафор необходимо освободить.

    Input:

       1 @Lock.locked
       2 class A(str):
       3     pass
       4 
       5 
       6 a, b = A("a"), A("b")
       7 a.lock = "S"       # Регистрация на семафор S
       8 b.lock = "S"       # Регистрация на семафор S
       9 print(a, a.lock)   # Успешный захват семафора S
      10 print(a, a.lock)   # Семафор S уже захвачен нами
      11 print(b, b.lock)   # Неуспешный захват семафора S
      12 del a.lock         # Освобождение семафора S
      13 print(b, b.lock)   # Успешный захват семафора S
      14 b.lock = "T"       # Регистрация на семафор T, освобождает предыдущий семафор
      15 print(b, b.lock)   # Успешный захват семафора T
      16 del b              # Удаление объекта-носителя освобождает семафор
      17 a.lock = "T"       # Регистрация на семафор T, освобождает предыдущий семафор
      18 print(a, a.lock)   # Успешный захват семафора T
    
    Output:

    a S
    a S
    b None
    b S
    b T
    a T

LecturesCMC/PythonIntro2021/10_SlotsDescriptorsDecorators (последним исправлял пользователь FrBrGeorge 2021-11-18 23:11:54)