-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Case of generic decorators not working on generic callables (ParamSpec) #17621
Comments
I think I have a similar problem when trying to create class-based decorators with alternate constructors: from __future__ import annotations
import collections.abc
import typing
P = typing.ParamSpec("P")
T_co = typing.TypeVar("T_co", covariant=True)
class MyDecorator(typing.Generic[P, T_co]):
def __init__(
self,
func: collections.abc.Callable[P, T_co],
*,
option: str | None = None,
) -> None:
self._attribute = option
@classmethod
def construct_with_configuration( # Argument 1 has incompatible type "Callable[[int], int]"; expected "Callable[[VarArg(Never), KwArg(Never)], Never]" [arg-type]
cls,
option: str,
) -> collections.abc.Callable[[collections.abc.Callable[P, T_co]], MyDecorator[P, T_co]]:
def decorator(func: collections.abc.Callable[P, T_co]) -> MyDecorator[P, T_co]:
return cls(func, option=option)
return decorator
@MyDecorator.construct_with_configuration(
option="a",
)
def a_function(a: int) -> int:
return a + 1
typing.reveal_type(a_function) # Revealed type is "MyDecorator[Never, Never]" |
I encountered a very similar problem when trying to overload a decorator to support it with kwargs or without. I think it all boils down to mypy resolving Example for completnessfrom __future__ import annotations
from collections.abc import Callable
from typing import ParamSpec, Protocol, TypeVar, overload
P = ParamSpec("P")
AB = TypeVar("AB", bound="A", covariant=True)
T = TypeVar("T")
class A:
def __init__(self) -> None:
return
class B(A):
pass
class ABFunc(Protocol[P, AB]):
__name__: str
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> AB: ...
@overload
def decorator(_func: ABFunc[P, AB]) -> ABFunc[P, AB]: ...
@overload
def decorator(
*, flag_a: bool = False, flag_b: tuple[AB, ...] = tuple()
) -> Callable[[ABFunc[P, AB]], ABFunc[P, AB]]: ...
def decorator(
_func: ABFunc[P, AB] | None = None,
*,
flag_a: bool = False,
flag_b: tuple[AB, ...] = tuple(),
) -> ABFunc[P, AB] | Callable[[ABFunc[P, AB]], ABFunc[P, AB]]:
def decorator(f: ABFunc[P, AB]) -> ABFunc[P, AB]:
print(flag_a)
print(flag_b)
return f
return decorator if _func is None else decorator(_func)
@decorator(flag_a=True) # works if e.g. 'flag_b=(B(),)' is added
def myfunc() -> B:
return B()
# reveal_type(myfunc) # 'ABFunc[[], B]' if flag_b is set otherwise 'ABFunc[Never, Never]' |
This describes an issue where a generic decorator that returns a generic sub-type of a "callable" (using
__call__
andParamSpec
) cannot be applied to a generic function. In the example below,Callable[_P, _R] -> Traceable[_P, _R]
works, butCallable[_P, _R] -> Decorated[_P, _R]
does not. It seems to work if either the decorated function is not generic (E.G.radius()
instead ofapply()
in the example), or if the return type of the decorator is a super-type of its argument (E.G.Traceable
instead ofDecorated
).Relevant closed issues
To Reproduce
https://mypy-play.net/?mypy=latest&python=3.12&gist=a8f681e6c14ec013bf3ae56c81fe94b2
Expected Behavior
Expected variables transferred from input generic callable to returned generic callable, even if the return is not a super-type of the input.
Actual Behavior
ParamSpec variables are not used to parameterize the returned generic if it is not a super-type of the input.
Your Environment
python -m mypy -v typehint_decorator.py
The text was updated successfully, but these errors were encountered: