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
.
Generics allow creating 'type variables' which can be used to create classes, functions & type aliases that don't need to explicitly define the types that they use. Generics makes it easier to write reusable code.
Generic functions: from typing import TypeVar, Sequence T = TypeVar('T') # Declare type variable def first(seq: Sequence[T]) -> T: return seq[0] def last(seq: Sequence[T]) -> T: return seq[-1] n = first([1, 2, 3]) # n has type int.
In Typescript, Type aliases give a type a new name. They are similar to interfaces in that they can be used to name primitives and any other kinds that you'd have to define by hand otherwise. Aliasing doesn't truly create a new type; instead, it gives that type a new name.
Generic Types Generics are not just used for function and method parameters. They can also be used to define classes that can contain, or work with, multiple types. These “generic types” allow us to state what type, or types, we want to work with for each instance when we instantiate the class.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With