Considere el siguiente código:
from typing import Callable, Any TFunc = Callable[..., Any] def get_authenticated_user(): return "John" def require_auth() -> Callable[TFunc, TFunc]: def decorator(func: TFunc) -> TFunc: def wrapper(*args, **kwargs) -> Any: user = get_authenticated_user() if user is None: raise Exception("Don't!") return func(*args, **kwargs) return wrapper return decorator @require_auth() def foo(a: int) -> bool: return bool(a % 2) foo(2) # Type check OK foo("no!") # Type check failing as intended
Esta pieza de código está funcionando como es debido. Ahora imagine que quiero extender esto, y en lugar de simplemente ejecutar func(*args, **kwargs)
, quiero func(*args, **kwargs)
el nombre de usuario en los argumentos. Por lo tanto, modifico la firma de la función.
from typing import Callable, Any TFunc = Callable[..., Any] def get_authenticated_user(): return "John" def inject_user() -> Callable[TFunc, TFunc]: def decorator(func: TFunc) -> TFunc: def wrapper(*args, **kwargs) -> Any: user = get_authenticated_user() if user is None: raise Exception("Don't!") return func(*args, user, **kwargs) # bool: print(username) return bool(a % 2) foo(2) # Type check OK foo("no!") # Type check OK <---- UNEXPECTED
No puedo encontrar una manera correcta de escribir esto. Sé que en este ejemplo, la función decorada y la función devuelta deberían tener técnicamente la misma firma (pero incluso eso no se detecta).
No puedes usar Callable
para decir nada sobre argumentos adicionales; No son generics. Su única opción es decir que su decorador toma un Callable
y que se devuelve un Callable
diferente.
En su caso, puede precisar el tipo de retorno con una barra de texto:
RT = TypeVar('RT') # return type def inject_user() -> Callable[[Callable[..., RT]], Callable[..., RT]]: def decorator(func: Callable[..., RT]) -> Callable[..., RT]: def wrapper(*args, **kwargs) -> RT: # ...
Incluso entonces, la función foo()
decorada resultante tiene una firma de escritura de def (*Any, **Any) -> builtins.bool*
cuando usa reveal_type()
.
Actualmente se están discutiendo varias propuestas para hacer que Callable
más flexible, pero aún no se han realizado. Ver
Callable
para poder especificar nombres y tipos de argumentos para algunos ejemplos El último en esa lista es un ticket paraguas que incluye su caso de uso específico, el decorador que altera la firma llamable:
Lío con el tipo de retorno o con argumentos
Para una función arbitraria todavía no puedes hacer esto, ni siquiera hay una syntax. Aquí estoy inventando algo de syntax para ello.