Considere el ejemplo de un decorador con tipo vinculado a ciertas clases .
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_functionAhora incluso tengo un generador que crea estos decoradores y quiero abreviar estos decoradores. Qué tipo le hago a las variables variables que almacena el decorador (ejemplo simplificado omitiendo el generador).
my_decorate: Callable[[Callable[[T], None]], Callable[[T], None]] = decorateEsto funciona, pero es torpe. Entonces la pregunta es:
¿Cómo puedo crear un alias de este tipo para evitar tener que escribir la firma completa?
Cosas que no funcionan:
TD = Callable[[Callable[[T], None]], Callable[[T], None]] my_decorate: TD[T] = decorator_variableda el 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) Por el contrario, puedo usar TD[T] como tipo de argumento para una función.
Simplemente usando my_decorate: TD = ... produce un error --strict
error: Missing type parameters for generic type "TD" Y ya no detecta aplicaciones incorrectas de my_decorate .
¿Qué pasa con esto? Es más corto que la firma completa:
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_variableComo en muchos casos, cuando Callable es demasiado limitado, use un Protocol en su lugar:
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 no es un tipo genérico y, por lo tanto, no necesita "rellenar" una variable de tipo. Se puede utilizar directamente como una anotación:
my_decorate: TD = decorate En particular, TD.__call__ sigue siendo un invocable genérico a pesar de que TD no es genérico. Su T se llena con el contexto de cada llamada según se desee.