Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to alias generic types for decorators

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.

like image 604
Zulan Avatar asked Nov 08 '21 19:11

Zulan


People also ask

How do you define a generic type in typescript?

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.

How do you write generic codes in Python?

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.

What is type alias in typescript?

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.

Does Python have generic classes?

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.


Video Answer


1 Answers

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.

like image 132
MisterMiyagi Avatar answered Oct 22 '22 14:10

MisterMiyagi