web-dev-qa-db-ja.com

Python 3デコレータのヒント

次のコードを検討してください。

_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
_

このコードは意図したとおりに機能しています。これを拡張したいと想像してください。func(*args, **kwargs)を実行するだけでなく、引数にユーザー名を挿入したいとします。したがって、関数のシグネチャを変更します。

_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)  # <- call signature modified

        return wrapper

    return decorator


@inject_user()
def foo(a: int, username: str) -> bool:
    print(username)
    return bool(a % 2)


foo(2)      # Type check OK
foo("no!")  # Type check OK <---- UNEXPECTED
_

これを入力する正しい方法がわかりません。この例では、装飾された関数と返された関数が技術的に同じシグネチャを持つ必要があることを知っています(ただし、それは検出されません)。

22
FunkySayu

Callableを使用して、追加の引数について何かを言うことはできません。それらは一般的ではありません。あなたの唯一の選択肢は、あなたのデコレータがCallableを取り、別のCallableが返されると言うことです。

あなたの場合、あなたはできますtypevarで戻り値の型を特定します:

_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:
            # ...
_

それでも、結果として装飾されたfoo()関数には、def (*Any, **Any) -> builtins.bool*を使用するときにreveal_type()の型指定シグネチャがあります。

Callableをより柔軟にするためのさまざまな提案が現在検討されていますが、それらはまだ実現していません。見る

いくつかの例について。そのリストの最後の1つは、特定のユースケース(呼び出し可能な署名を変更するデコレーター)を含む包括的なチケットです。

戻り値型または引数を混乱させる

任意の関数については、まだこれを行うことはできません-構文すらありません。これがそのための構文の一部です。

14
Martijn Pieters