-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in 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: