Вот классический пример декоратора, реализованного классом:
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?
@anthonysottile Но ParamSpec
в Generic
поставить нельзя. Кстати, подпись еще не скопирована.
«ParamSpec
нельзя положить в Generic
»: Кто вам это сказал?
@InSync Я обновил вопрос. Такое поведение, похоже, не распознается PyCharm должным образом. Когда я использую декоратор функции, например def dec(func: Callable[PT, RT]) -> Callable[PT, RT}
, PyCharm всегда показывает список аргументов. Но на этот раз это видно только *args, **kwargs
.
Я думаю, что это PyCharm. Pylance (который поддерживается Pyright), кажется, прекрасно справляется с этим на VSCode.
Вы хотите явно заменить функцию вызываемым объектом с определенной сигнатурой вместо замены функции экземпляром вызываемого класса, который работает так же, но не имеет общей сигнатуры.
Это показывает разницу:
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, но в этом нет необходимости.
IOW, OP заменяет функцию вызываемым объектом с определенной сигнатурой.
Да, я думаю, это справедливо: стоит подумать о том, чтобы сообщить о поведении команде JetBrains как об ошибке. Я согласен, что исходный источник должен иметь тот же результат - вывод PyCharm не дает правильного вывода. В текущей версии я бы, вероятно, выбрал предложенное мной решение, если только максимальная производительность не является проблемой, и в этом случае можно рассмотреть добавление аннотации, подавляющей предупреждение. При этом я считаю, что исправление этой функции поможет читателю лучше понять полученный тип, но это мнение.
Это вина PyCharm?
Да. Ваш код должен был работать как есть, в результате чего подпись x: int, y: int
отображалась во всплывающем окне.
Средство проверки типов PyCharm общеизвестно плохо справляется с ParamSpec
и другими немного сложными ситуациями. Как правило, лучше всего отключить его напыщенную речь и использовать более совершенную проверку типов.
базовый пример
ParamSpec
здесь хорошо подойдет