Как написать подсказку типа для декорирования, реализованного классом

Вот классический пример декоратора, реализованного классом:

class Decorator:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        self.func(*args, **kwargs)

Как сделать так, чтобы __call__ имел ту же подпись и подсказки по типу, что и func?

Я попробовал следующий код:

from typing import Callable, TypeVar, ParamSpec, Generic

PT = ParamSpec('PT')
RT = TypeVar('RT')


class Decorator(Generic[PT, RT]):
    def __init__(self, func: Callable[PT, RT]) -> None:
        self.func = func

    def __call__(self, *args: PT.args, **kwargs: PT.kwargs) -> RT:
        return self.func(*args, **kwargs)


@Decorator
def add(x: int, y: int) -> int:
    return x + y

Но мне не удалось получить правильный список аргументов add в PyCharm.

Это вина PyCharm?

базовый пример ParamSpec здесь хорошо подойдет

anthony sottile 20.06.2024 05:31

@anthonysottile Но ParamSpec в Generic поставить нельзя. Кстати, подпись еще не скопирована.

OrthoPole 20.06.2024 05:44

«ParamSpec нельзя положить в Generic»: Кто вам это сказал?

InSync 20.06.2024 05:47

@InSync Я обновил вопрос. Такое поведение, похоже, не распознается PyCharm должным образом. Когда я использую декоратор функции, например def dec(func: Callable[PT, RT]) -> Callable[PT, RT}, PyCharm всегда показывает список аргументов. Но на этот раз это видно только *args, **kwargs.

OrthoPole 20.06.2024 05:57

Я думаю, что это PyCharm. Pylance (который поддерживается Pyright), кажется, прекрасно справляется с этим на VSCode.

juanpa.arrivillaga 20.06.2024 07:28
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
5
71
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

Это показывает разницу:

from typing import Callable, TypeVar, ParamSpec, Generic

PT = ParamSpec('PT')
RT = TypeVar('RT')


class Decorator(Generic[PT, RT]):
    def __init__(self, func: Callable[PT, RT]) -> None:
        self.func = func

    def __call__(self, *args: PT.args, **kwargs: PT.kwargs) -> RT:
        return self.func(*args, **kwargs)


def decorator(func: Callable[PT, RT]) -> Callable[PT, RT]:
    return Decorator(func)


@decorator
def add_func_decorated(x: int, y: int) -> int:
    return x + y


@Decorator
def add_class_decorated(x: int, y: int) -> int:
    return x + y


print(add_func_decorated(1, 2))
print(add_class_decorated(1, 2))

Вы обнаружите, что и подсказки типов, и проверка типов работают корректно для add_func_decorated, в том числе и в PyCharm. Хотя проверка типа add_class_decorated работает, но подсказка типа отображается так, как вы показали в своем вопросе.

Я думаю, это обходной путь для pycharm, но в этом нет необходимости.

juanpa.arrivillaga 20.06.2024 07:26

IOW, OP заменяет функцию вызываемым объектом с определенной сигнатурой.

juanpa.arrivillaga 20.06.2024 07:30

Да, я думаю, это справедливо: стоит подумать о том, чтобы сообщить о поведении команде JetBrains как об ошибке. Я согласен, что исходный источник должен иметь тот же результат - вывод PyCharm не дает правильного вывода. В текущей версии я бы, вероятно, выбрал предложенное мной решение, если только максимальная производительность не является проблемой, и в этом случае можно рассмотреть добавление аннотации, подавляющей предупреждение. При этом я считаю, что исправление этой функции поможет читателю лучше понять полученный тип, но это мнение.

Grismar 20.06.2024 07:48

Это вина PyCharm?

Да. Ваш код должен был работать как есть, в результате чего подпись x: int, y: int отображалась во всплывающем окне.

Средство проверки типов PyCharm общеизвестно плохо справляется с ParamSpec и другими немного сложными ситуациями. Как правило, лучше всего отключить его напыщенную речь и использовать более совершенную проверку типов.

Другие вопросы по теме