Различия между версиями 22 и 23
Версия 22 от 2022-11-30 20:43:21
Размер: 8162
Редактор: FrBrGeorge
Комментарий:
Версия 23 от 2022-11-30 20:54:28
Размер: 8506
Редактор: FrBrGeorge
Комментарий:
Удаления помечены так. Добавления помечены так.
Строка 128: Строка 128:
    * Подсказка: `list[int].__origin__`     * Подсказка: такая конструкция имеет тип `types.GenericAlias`, а тип объекта хранится в поле `.__origin__`
   * Если ни сама аннотация, ни тип, хранящийся в дженерике не совпадают ни с типом объекта, проверка соответствия неуспешна

11.29 Метаклассы и аннотации

FIXME: "вроде просто" сформулированные упражненьки на метаклассы/аннотации оказываются сложными из-за нагрузки на голову, ибо сам материал сложный.

FIXME: Пока просто публикуем планируемые результаты упражнений, к следующему году надо переделать: забить на mypy и вставить больше демо

FIXME: Просмотреть на предмет достаточности примеров для решения Д/З

Метаклассы

  • Зачем (не) нужны
  • {OK} Создание класса с помощью type(имя, родители, атрибуты)

  • {i} Создать класс C, который работал бы так:

       1 >>> C.var
       2 100500
       3 >>> c = C(123)
       4 >>> c.var
       5 123
       6 
    
    • Если получится — в одну строку (__init__ задать лямбдой + setattr())

  • B) «Шумный» метаклаcc, в котором переопределены 4 метода

  • {i} Написать метакласс 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
      

Аннотации

  • зачем (не) нужны)
  • {OK} Написать простой аннотированный класс и посмотреть в нём аннотации

  • {OK} как из кода добраться до аннотации функции (метода класса)

  • {i} TODO пока публикуем результат. Написать класс, в котором __init__() , проверяет с помощью isinstance() тип единственного передаваемого параметра согласно аннотации; иначе — TypeError()

       1 from inspect import get_annotations as ann
       2 
       3 class C:
       4 
       5     def __init__(self, v: int):
       6         a = ann(self.__class__.__init__)
       7         if not isinstance(v, a["v"]):
       8             raise TypeError(f"v must be {a}")
    

TODO На этом всё застряло

  • {OK} Аннотации составных типов данных (list[int])

  • {i} Модифицировать предыдущее упражнение так, чтобы __init__() допускал в качестве параметра только итерируемую последовательность данных (как минимум, список или кортеж) определённого типа. Тип последовательности и тип данных задаётся аннотацией

mypy/mypyc

  • {OK} Установка и использование mypy

  • {i} Написать функцию, которая вычисляет число Пи по формуле лейбница, добиться её работоспособности и отсутствия ошибок mypy --strict

  • {OK} Использование mypyc

  • {i} Сравнение производительности скомпилированной и интерпретируемой программы

    • Возьмите пример с рекурсивной сортировкой слиянием, поместите его в файл 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 $ python -m timeit -s 'import merge' 'merge.merge_sort(random.choices(range(100),k=30000))'
         2 $ mypyc merge.py
         3 $ python -m timeit -s 'import merge' 'merge.merge_sort(random.choices(range(100),k=30000))'
      
    • сравните выведенные при запусках длительности выполнения

Д/З

Напомнить людям про перекрёстное тестирование

  • <!> Задача_1. Написать метакласс dump, который "обмазывает" все имеющиеся в словаре методы класса выводом имени метода и значений его параметров (*args, **kwargs)

    • через параметры методов передаются только значения типов str, int, float и bool (и никаких других типов)

    • методы задаются только классическим путём (def метод()…)

    • не используются декораторы методов (например, нет статических методов и методов класса)
    • формат вывода: имя_метода: кортеж_args, словарь_kwargs

    • вывод должен осуществляться при каждом вызове метода
    • Пример:
         1 class C(metaclass=dump):
         2     def __init__(self, val):
         3         self.val = val
         4 
         5     def add(self, other, another=None):
         6         return self.val + other + (another or self.val)
         7 
         8 c = C(10)
         9 print(c.add(9))
        10 print(c.add(9,another=10))
      
      __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

LecturesCMC/PythonIntro2022/Prac/12_MetaclassAnnotations (последним исправлял пользователь FrBrGeorge 2022-11-30 20:54:28)