Задача 6D. Проверка типов

Входной файл:Стандартный вход   Ограничение времени:1 сек
Выходной файл:Стандартный выход   Ограничение памяти:512 Мб

Условие

Требуется реализовать на языке Python функцию-декоратор check_types, выполняющую проверку параметров и возвращаемого значения функции на соответствие аннотации при вызове функции. Функция должна поддерживать List, Dict, Any, Tuple, Optional, Union, Set (см. typing) и проверку на полное соответствие типу, при этом объекты классов наследников разрешается передавать как объекты классов родителей. Если вызов функции не проходит проверку, необходимо вызвать исключение TypeCheckError, имеющее следующий интерфейс

class TypeCheckError(BaseException):
    
    # Имя аргумента, которому было передано значение неверного типа. `return`, если функция вернула значение неверного типа
    name: str

Функция также должна иметь возможность декорировать классы, при этом добавляется проверка на каждую функцию, определённую в классе. После декорирования такой класс должен быть доступен для использования в аннотациях других классов и функций.

Все тесты используют следующий функционал, который будет использоваться по умолчанию, начиная с Python 3.11

from __future__ import annotations

При решении задачи допускается использование функции eval.

Формат выходных данных

Код решения должен содержать определение функции check_types и класса TypeCheckError, а также все вспомогательные переменные, функции и классы.

Примеры тестов

Стандартный вход Стандартный выход
1
@check_types
def add(a: int, b: int) -> int:
    return a + b

try:
    print(add(1, 2))
    print(add('1', '2'))
except TypeCheckError as e:
    print(f'Failed {e.name}')
3
Failed a
2
@check_types
def sqr(a: float) -> int:
    return a * a

try:
    sqr(2.)
except TypeCheckError as e:
    print(f'Failed {e.name}')
Failed return
3
@check_types
class Number:

    def __init__(self: Number, value: int):
        self.value = value

    def __add__(self: Number, other: Number) -> Number:
        return Number(self.value + other.value)

try:
    print((Number(1) + Number(2)).value)
    print((Number(1) + 2).value)
except TypeCheckError as e:
    print(f'Failed {e.name}')
3
Failed other
4
@check_types
def pow(x: Union[int, List[int]], p: int) -> Union[int, List[int]]:
    if isinstance(x, list):
        return [i ** p for i in x]

    return x ** p

try:
    print(pow(2, 2))
    print(*pow([1, 2, 3], 3))
    print(*pow((1, 2, 3), 3))
except TypeCheckError as e:
    print(f'Failed {e.name}')
4
1 8 27
Failed x

0.122s 0.022s 15