Zulan
Zulan

Reputation: 22670

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.

Upvotes: 11

Views: 815

Answers (2)

MisterMiyagi
MisterMiyagi

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

gogognome
gogognome

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

Related Questions