Differences between revisions 1 and 2
Revision 1 as of 2020-11-17 07:56:14
Size: 7546
Editor: FrBrGeorge
Comment:
Revision 2 as of 2020-11-17 09:48:48
Size: 12189
Editor: FrBrGeorge
Comment:
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
= Слоты, дескрипторы, декораторы, метаклассы = = Слоты, дескрипторы, декораторы =
Line 3: Line 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()]]
Line 71: Line 197:
 * Зачем в ''каждом'' объекте есть ''свой'' `__dict__`, если ''имена'' полей всех объектов совпадают?  * Зачем в ''каждом'' объекте есть ''свой'' `__dict__`, если имена полей всех объектов ''обычно'' совпадают?
Line 104: Line 230:
== Декораторы ==
Что, если мы хотим «обмазать» все вызовы некоторой функции отладочной информацией?
{{{#!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|примеры]]

∃ декораторы методов в классах. Но это потом.
== Стандартные декораторы ==
 * [[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]] — обёртка вокруг дескриптора
 {{{#!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 ☺)
  * Обратите внимание, что в примере `_var`, а не `__var` (на самом деле могло быть что угодно)
 * [[py3doc:dataclasses]], [[py3doc:functools]], [[py3doc:contextlib]]…

'''Вот отсюда точно не успеем'''
Line 204: Line 307:
 * Создание класса с помощью [[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__()]]

<!> Не путать с наследованием!
Line 225: Line 349:
'''TODO''' про `.__new_()` подробнее

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

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

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

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

  • вместо объекта-функции @декоратор мы пишем вызов этого объекта @п_декоратор(параметры), значит, в этом месте произойдёт вызов п_декоратор(параметры), а вот то, что оно вернёт, и послужит декоратором:

       1 def multicall(times):
       2     def decorator(fun):
       3         def newfun(*args):
       4             return [fun(*args) for i in range(times)]
       5         return newfun
       6     return decorator
       7 
       8 @multicall(5)
       9 def simplefun(N):
      10     return N*2+1
      11 
      12 print(*simplefun(4))
    

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

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

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

  • Декоратор метода — это то же самое, что декоратор функции
  • Класс — это 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

Дескрипторы

Вместо .__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

Обратите внимание: дескриптор — поле класса, так что

   1 e = C("Ye")
   2 d.data = 100500
   3 print(d.data)
   4 print(e.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__.

Слоты

   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 

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

  • @classmethod и @staticmethod:

       1 class C:
       2     def fun(*args):
       3         print("Normal:", args)
       4         
       5     @classmethod
       6     def cfun(*args):
       7         print("Class:", args)
       8 
       9     @staticmethod
      10     def sfun(*args):
      11         print("Static:", args)
    
       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 — обёртка вокруг дескриптора

       1 class C:
       2     def __init__(self):
       3         self._var = None
       4 
       5     @property
       6     def x(self):
       7         """I'm the 'x' property."""
       8         return self._var
       9 
      10     @x.setter
      11     def x(self, value):
      12         self._var = value
      13 
      14     @x.deleter
      15     def x(self):
      16         del self._var
    
    • Обратите внимание на троекратное def x( — не надо придумывать ненужные имена (нельзя, actually ☺)

    • Обратите внимание, что в примере _var, а не __var (на самом деле могло быть что угодно)

  • dataclasses, functools, contextlib

Вот отсюда точно не успеем

Метаклассы (одним глазом)

  • Про метаклассы в документации

  • На realpython.com

  • Метакласс — конструктор классов
  • <!> Никто не запрещает делать конструктор классов так:

   1 def fc(st):
   2     class _bzz:
   3         def __init__(self):
   4             self.st = st
   5     return _bzz
   6 
   7 C = fc(10500)
   8 c = C()
   9 print(c.st)

Все заметили, что это почти декоратор? Что ему не хватает до того, чтобы быть настоящим декоратором?

В любом случае есть несколько вариантов, когда такой механизм не работает. Есть более правильный способ — метаклассы.

  • Создание класса с помощью type(name, bases, dict)

       1  class C:
       2     pass
    
    • это
       1  C = type("C", (), {})
    
  • type — это просто класс такой ⇒

       1  class overtype(type):
       2     pass
       3 
       4  T = overtype("Boo", (), {})
    
  • ну так вот, «T = overtype(…)» == «class T(metaclass=overtype): …»

  • определённые правила для __init__() и __new__()

<!> Не путать с наследованием!

   1 from time import ctime
   2 
   3 class D(type):
   4     def __str__(self):
   5         self.__date = ctime()
   6         return f"<class {self.__class__.__name__} created at {self.__date}>"
   7 
   8 class C(metaclass=D):
   9     pass
  10 
  11 class E:
  12     pass

   1 >>> c, e = C(), E()
   2 >>> print(C, E)
   3 <class D created at Sun Dec  1 18:17:59 2019> <class '__main__.E'>
   4 

TODO про .__new_() подробнее

Д/З

TODO

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