Подсказки типов Python для декораторов классов со ссылками на себяPython

Программы на Python
Anonymous
Подсказки типов Python для декораторов классов со ссылками на себя

Сообщение Anonymous »

Моя конечная цель — написать систему, позволяющую легко записывать вызовы функций (в частности, методов класса).
Я начал с написания класса Loggable с методом-оболочкой это позволяет мне украшать методы подклассов и записывать их вызовы

Код: Выделить всё

Param = ParamSpec("Param")
RetType = TypeVar("RetType")

CountType = TypeVar("CountType", bound="FunctionCount")

class FunctionCount(Generic[CountType]):
def __init__(self, count_dict: dict[str, int]) -> None:
self.count_dict = count_dict

@staticmethod
def count(
func: Callable[Concatenate[CountType, Param], RetType],
) -> Callable[Concatenate[CountType, Param], RetType]:
def wrapper(
self: CountType, *args: Param.args, **kwargs: Param.kwargs
) -> RetType:
function_name = f"{self.__class__.__name__}.{func.__name__}"
if function_name not in self.count_dict:
self.count_dict[function_name] = 0
self.count_dict[function_name] += 1

return func(self, *args, **kwargs)

return wrapper
Теперь я могу писать подклассы и записывать их вызовы:

Код: Выделить всё

class A(FunctionCount):
def __init__(self, count_dict: dict[str, int]) -> None:
super().__init__(count_dict)

@FunctionCount.count
def func(self) -> None:
pass

@FunctionCount.count
def func2(self) -> None:
pass

count_dict: dict[str, int] = {}

a = A(count_dict)

a.func()
a.func()
a.func2()

print(count_dict)
assert count_dict == {"A.func": 2, "A.func2": 1}
Это работает очень хорошо, и я был рад.
Но потом я подумал, что было бы неплохо иметь собственные имена для методов, поэтому я заменил обертку на декоратор

Код: Выделить всё

class FunctionCount(Generic[CountType]):
def __init__(self, count_dict: dict[str, int]) -> None:
self.count_dict = count_dict

@staticmethod
def count(
f_name: str | None = None,
) -> Callable[
[Callable[Concatenate[CountType, Param], RetType]],
Callable[Concatenate[CountType, Param], RetType],
]:
def decorator(
func: Callable[Concatenate[CountType, Param], RetType],
) -> Callable[Concatenate[CountType, Param], RetType]:
def wrapper(
self: CountType, *args: Param.args, **kwargs: Param.kwargs
) -> RetType:
function_name = f_name or f"{self.__class__.__name__}.{func.__name__}"
if function_name not in self.count_dict:
self.count_dict[function_name] = 0
self.count_dict[function_name] += 1

return func(self, *args, **kwargs)

return wrapper

return decorator
Тогда мне просто пришлось изменить вызовы декоратора

Код: Выделить всё

class A(FunctionCount):
def __init__(self, count_dict: dict[str, int]) -> None:
super().__init__(count_dict)

@FunctionCount.count()
def func(self) -> None:
pass

@FunctionCount.count("custom_name")
def func2(self) -> None:
pass

a.func()
a.func()
a.func2()

print(count_dict)
assert count_dict == {"A.func": 2, "custom_name": 1}
Эти сценарии также работают очень хорошо, но теперь с mypy у меня трудные времена.
Когда я вызываю метод a.func, я получаю следующую ошибку mypy:
Недопустимый собственный аргумент «A» для атрибута функции «func» с типом «Callable[[Never], None]» mypy(misc)
Думаю, использование декоратора вместо оболочки вызвало эту ошибку, но я не могу понять, почему и что мне следует сделать, чтобы ее исправить.
Кто-нибудь знает, как чтобы таким образом иметь правильно набранный декоратор?

Подробнее здесь: https://stackoverflow.com/questions/788 ... references

Вернуться в «Python»