Sugerencias tipo Python 3 para decorador.

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

  • Permitir variados generics.
  • Propuesta: Generalizar Callable para poder especificar nombres y tipos de argumentos
  • TypeVar para representar los argumentos de un Callable
  • Función de apoyo a los decoradores de forma excelente.

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.