12214
Комментарий:
|
← Версия 10 от 2020-11-21 20:30:16 ⇥
12883
|
Удаления помечены так. | Добавления помечены так. |
Строка 132: | Строка 132: |
* [[py3how:descriptor.html|подробрая статья]] | * [[py3how:descriptor.html|подробрая статья]] (рекомендуется) |
Строка 137: | Строка 137: |
* ''Протокол дескриптора'' — объект с методами `.__get__()`, `.__set__()` и `.__delete__()` (если без `__set__()`, значит, это не данные, а, скажем, функция) | * ''Протокол дескриптора'' — объект с методами `.__get__()`, `.__set__()` и `.__delete__()` * если без `__set__()`, значит, это не данные, а, скажем, метов (т. н. ''non-data descriptor'') |
Строка 140: | Строка 141: |
* конкретный экземпляр передаётся вторым параметром (`None`, если вызывается как метод класса), так что при желании можно различить * Имеет ''преимущество'' перед полем экземпляра (в отличие от обычных полей класса) {{{#!highlight python3 |
* конкретный экземпляр передаётся вторым параметром * ''тип'' (класс) экземпляра передаётся третьим параметром в `.__get__()` * Например, если пытаться прочесть поле класса `класс.дескриптор`, второй параметр будет равен `None` * Если задан `.__set__()`, Имеет ''преимущество'' перед полем экземпляра (в отличие от обычных полей класса) * если не задан, т. е. для non-data, то, конечно, первое же связывание заведёт на этом месте обычное поле экземпляра {{{#!highlight python |
Строка 144: | Строка 148: |
value = None | |
Строка 147: | Строка 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 |
Строка 165: | Строка 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> |
Строка 185: | Строка 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` — это поле ''конкретного объекта'', в которое ходит дескриптор |
Строка 263: | Строка 256: |
* (!) Важное отличие: `property` — это поле `объекта`, а не класса, т. е. именно реализация шаблона [[RW:Геттер_(программирование)|getter/setter]] в чистом виде | |
Строка 282: | Строка 276: |
* Обратите внимание, что в примере `_var`, а не `__var` (на самом деле могло быть что угодно) | |
Строка 284: | Строка 277: |
'''Вот отсюда точно не успеем''' == Метаклассы (одним глазом) == * Про метаклассы [[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_()` подробнее |
* В частности, [[py3doc:functools#functools.wraps|@functools.wraps]], который помогает сохранить исходное имя и строку документации функции ##'''Вот отсюда точно не успеем''' ##== Метаклассы (одним глазом) == ## * Про метаклассы [[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_()` подробнее |
Строка 352: | Строка 346: |
1.#0 Прочитать про всё, упомянутое выше. Пощёлкать примеры по каждой теме. | |
Строка 353: | Строка 348: |
1. <<EJCMC(148, TypeCheck, Проверка типов)>> 1. <<EJCMC(148, FieldCounter, Статистика с полей)>> |
Слоты, дескрипторы, декораторы
Расширения объектной модели 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
EJudge: TypeCheck 'Проверка типов'
Написать параметрический декоратор TypeCheck(последовательность_типов, тип_результата), который бросает исключение TypeError при вызове функции со следующим сообщением:
"Type of argument Номер is not Тип", если не совпадает тип позиционного параметра функции и соответствующий ему по порядку тип в последовательности_типов
"Type of argument 'Имя' is not Тип", если не совпадает тип именного параметра функции и соответствующий ему тип в последовательности_типов.
Типы именованных параметров перечислены в конце последовательности_типов
- Типы именованных параметров проверяются в порядке их появления при вызове функции
"Type of result is not Тип", если тип возвращённого функцией значения не совпадает с типом_результата
"Function функция must have число arguments" — если количество переданных функции параметров (включая переданные по умолчанию) не соответствует длине последовательности_типов
- Сначала проверяются параметры в порядке описания в функции, затем вызывается функция, после чего проеряется результат. Ислкючение возникает при первом несовпадении типа.
18 Type of argument 2 is not <class 'str'>
18 Type of argument 2 is not <class 'str'>
EJudge: FieldCounter 'Статистика с полей'
Написать функцию,Stat() которая умеет выполнять две роли:
С одним параметром Stat(класс) — работает как декоратор класса
- Добавляет в объекты класса сбор статистики по всем полям данных
упомянутым в самом классе (т. е. встречающимся в vars(класс))
имя которых не начинается с "_"
По всем остальным атрибутам (методам, спецметодам и т. п., а так же атрибутам, динамически добавленным в __init__() и других методах) статистика не ведётся
- Добавляет в объекты класса сбор статистики по всем полям данных
С двумя параметрами Stat(объект, "поле") — выводит статистику использования поля поле: два целых числа (количество чтений, количество записей)
объект — экземпляр класса, декорированного с помощью Stat
поле — поле этого класса
- Если статистика по данному полю не ведётся, или поля не существует, обя значения равны 0
Экземпляр класса инициализируется с помощью __init__(), т. е. класс не является потомком встроенного класса, не переопределяет __new__(), __getattribute__() и т. п., изначально не содержит слотов/дескрипторов.
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"))
(0, 0) (0, 1) (1, 0) (1, 1) (1, 2) (3, 0) (0, 0)