Reputation: 22670
Consider the example of a typed decorator bound to certain classes.
import unittest
from typing import *
T = TypeVar("T", bound=unittest.TestCase)
def decorate(func: Callable[[T], None]) -> Callable[[T], None]:
def decorated_function(self: T) -> None:
return func(self)
return decorated_function
Now I even have a generator that creates these decorators and want to shorthand these decorators. What type do I to the variables variables storing the decorator (simplified example omitting the generator).
my_decorate: Callable[[Callable[[T], None]], Callable[[T], None]] = decorate
This works, but is clunky. So the question is:
How can I alias this type to avoid having to write the the full signature?
Things that don't work:
TD = Callable[[Callable[[T], None]], Callable[[T], None]]
my_decorate: TD[T] = decorator_variable
Gives the error
error: Type variable "mypytest.T" is unbound
note: (Hint: Use "Generic[T]" or "Protocol[T]" base class to bind "T" inside a class)
note: (Hint: Use "T" in function signature to bind "T" inside a function)
In contrast, I can use TD[T]
as argument type for a function.
Just using my_decorate: TD = ...
yields a --strict
error
error: Missing type parameters for generic type "TD"
And it no longer detects wrong applications of my_decorate
.
Upvotes: 11
Views: 815
Reputation: 51989
As in many cases, when Callable
is too limited use a Protocol
instead:
class TD(Protocol):
"""Type of any callable `(T -> None) -> (T -> None)` for all `T`"""
def __call__(self, __original: Callable[[T], None]) -> Callable[[T], None]:
...
TD
is not a generic type and thus does not need "filling in" a type variable. It can be used directly as an annotation:
my_decorate: TD = decorate
Notably, TD.__call__
is still a generic callable even though TD
is not generic. Its T
is filled by the context of each call as desired.
Upvotes: 4
Reputation: 759
What about this? It is shorter than the full signature:
import unittest
from typing import *
T = TypeVar("T", bound=unittest.TestCase)
def decorate(func: Callable[[T], None]) -> Callable[[T], None]:
def decorated_function(self: T) -> None:
return func(self)
return decorated_function
decorator_variable: Callable[[Callable[[T], None]], Callable[[T], None]] = decorate
U = Callable[[T], None]
my_decorate: Callable[[U[T]], U[T]] = decorator_variable
Upvotes: 0