简体   繁体   中英

Python Async decorator preserve typing

For the following file:

from abc import ABC, abstractmethod
from typing import Any, Awaitable, Callable, TypeVar, cast

T = TypeVar('T')


def dec(args: Any) -> Callable[..., Awaitable[T]]:
    def dec2(f: Callable[..., Awaitable[T]]) -> Awaitable[T]:
        async def wrapper(*args:Any, **kwargs:Any) -> T:
            print(args)

            return cast(T, await f(*args, **kwargs))
        return cast(Awaitable[T], wrapper)
    return dec2


class A(ABC):
    @abstractmethod
    async def fn(self) -> 'bool':
        pass

class B(A):
    @dec('hi')
    async def fn(self) -> 'bool':
        return True


class C(A):
    @dec('hi')
    async def fn(self) -> 'bool':
        return False

I am receiving the following mypy errors:

$ mypy typetest.py
typetest.py:24: error: Signature of "fn" incompatible with supertype "A"
typetest.py:30: error: Signature of "fn" incompatible with supertype "A"
Found 2 errors in 1 file (checked 1 source file)

How does the typing need to work in order to preserve the class signature and not receive the mypy error.

This is on python3.7 with mypy 0.790

TLDR: A function declaration async def name(parameters) -> R: creates an object of type (parameters) -> Awaitable[R] , not Awaitable[R] . This means the cast cast(Awaitable[T], wrapper) is wrong and should be omitted, and the various Callable return types must be adjusted as well.


A naive decorator to show the execution of an async def function (roughly dec2 ) would look like this:

def show_call(f: Callable[..., Awaitable[T]]) -> Callable[..., Awaitable[T]]:
    async def wrapper(*args: Any, **kwargs: Any) -> T:
        print(f"Executing {f.__name__}")
        return await f(*args, **kwargs)
    return wrapper

This receives an callable matching async def and returns a callable matching async def . In other words, it preserves the general type of "an async function".
Note that there is no cast needed.

Since the Callable argument was specified as ... , the parameter information is lost. This can be fixed with PEP 612 (Python 3.10 / typing_extensions ) parameter variables, which are similar to type variables.

from typing import Any, Awaitable, Callable, TypeVar, ParamSpec

T = TypeVar('T')    # the callable/awaitable return type
P = ParamSpec('P')  # the callable parameters


def dec(message: Any) -> Callable[[Callable[P, Awaitable[T]]], Callable[P, Awaitable[T]]]:
    def dec2(f: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
        async def wrapper(*args: Any, **kwargs: Any) -> T:
            print(message)
            return await f(*args, **kwargs)
        return wrapper
    return dec2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM