11.29 Метаклассы и аннотации
FIXME: "вроде просто" сформулированные упражненьки на метаклассы/аннотации оказываются сложными из-за нагрузки на голову, ибо сам материал сложный.
FIXME: Пока просто публикуем планируемые результаты упражнений, к следующему году надо переделать: забить на mypy и вставить больше демо
FIXME: Просмотреть на предмет достаточности примеров для решения Д/З
Метаклассы
- Зачем (не) нужны
Создание класса с помощью type(имя, родители, атрибуты)
Создать класс C, который работал бы так:
Если получится — в одну строку (__init__ задать лямбдой + setattr())
«Шумный» метаклаcc, в котором переопределены 4 метода
Написать метакласс Both, при создании класса с помощью которого можно передавать два именных параметра: final= и single=. Если соответствующий параметр непуст, порождаемый класс не может выступать в роли предка и/или является синглтоном
- FIXME: упражненька тяжеловата оказалась
1 class Both(type): 2 _instance = None 3 4 def __call__(cls, *args, **kw): 5 if not cls._single or cls._instance is None: 6 cls._instance = super().__call__(*args, **kw) 7 return cls._instance 8 9 def __new__(metacls, name, parents, namespace, **flags): 10 cls = super().__new__(metacls, name, parents, namespace) 11 cls._single = flags.get("single", False) 12 cls._final = flags.get("final", False) 13 for cls in parents: 14 if isinstance(cls, Both) and cls._final: 15 raise TypeError(f"{cls.__name__} is final") 16 return cls
- FIXME: упражненька тяжеловата оказалась
Аннотации
- зачем (не) нужны)
Написать простой аннотированный класс и посмотреть в нём аннотации
как из кода добраться до аннотации функции (метода класса)
TODO пока публикуем результат. Написать класс, в котором __init__() , проверяет с помощью isinstance() тип единственного передаваемого параметра согласно аннотации; иначе — TypeError()
TODO На этом всё застряло
Аннотации составных типов данных (list[int])
Модифицировать предыдущее упражнение так, чтобы __init__() допускал в качестве параметра только итерируемую последовательность данных (как минимум, список или кортеж) определённого типа. Тип последовательности и тип данных задаётся аннотацией
mypy/mypyc
Установка и использование mypy
Написать функцию, которая вычисляет число Пи по формуле лейбница, добиться её работоспособности и отсутствия ошибок mypy --strict
Использование mypyc
Сравнение производительности скомпилированной и интерпретируемой программы
Возьмите пример с рекурсивной сортировкой слиянием, поместите его в файл merge.py:
1 import random 2 3 def merge(sp1, sp2): 4 result = [] 5 while sp1 and sp2: 6 if sp1[0] < sp2[0]: 7 h, *sp1 = sp1 8 else: 9 h, *sp2 = sp2 10 result.append(h) 11 result.extend(sp1) 12 result.extend(sp2) 13 return result 14 15 def merge_sort(sp): 16 if len(sp) <= 1: 17 return sp 18 m = len(sp)//2 19 sp1, sp2 = merge_sort(sp[:m]), merge_sort(sp[m:]) 20 return merge(sp1, sp2)
запустите пример в интерпретаторе Python, затем скомпилируйте с помощью mypyc и запустите скомпилированный пример.
- сравните выведенные при запусках длительности выполнения
Д/З
Напомнить людям про перекрёстное тестирование
Задача_1. Написать метакласс dump, который "обмазывает" все имеющиеся в словаре методы класса выводом имени метода и значений его параметров (*args, **kwargs)
через параметры методов передаются только значения типов str, int, float и bool (и никаких других типов)
методы задаются только классическим путём (def метод()…)
- не используются декораторы методов (например, нет статических методов и методов класса)
формат вывода: имя_метода: кортеж_args, словарь_kwargs
- вывод должен осуществляться при каждом вызове метода
- Пример:
- →
__init__: (10,), {} add: (9,), {} 29 add: (9,), {'another': 10} 29
Задача_2. Написать метакласс check, который добавляет в создаваемый класс метод check_annotations(), возвращающий True, если типы всех аннотированных полей объекта (а) существуют и (б) соответствуют аннотации, и False в противном случае
Если поле имеет составной тип, например list[int], проверять только соответствие типу list
Подсказка: такая конструкция имеет тип types.GenericAlias, а тип объекта хранится в поле .__origin__
- Если ни сама аннотация, ни тип, хранящийся в дженерике не совпадают ни с типом объекта, проверка соответствия неуспешна
- Методы проверять не надо
- Пример:
1 class C(metaclass=check): 2 A: list[int] 3 B: str = "QQ" 4 5 c = C() 6 print(c.check_annotations()) 7 c.A = "ZZ" 8 print(c.check_annotations()) 9 c.A = [100500, 42, 0] 10 print(c.check_annotations()) 11 c.B = type("Boo",(str,),{})(42) 12 print(c.check_annotations()) 13 c.A = ["FALSE"] 14 print(c.check_annotations())
- →
False False True True True