Oca*_*b19 5 python typing python-3.10
我遵循PEP 0612(动机部分的最后一个)中的示例来创建一个可以向函数添加默认参数的装饰器。问题是,提供的示例仅适用于函数而不适用于方法,因为Concate不允许self在定义中的任何位置插入。
考虑这个例子,作为 PEP 中的例子的改编:
def with_request(f: Callable[Concatenate[Request, P], R]) -> Callable[P, R]:
def inner(*args: P.args, **kwargs: P.kwargs) -> R:
return f(*args, request=Request(), **kwargs)
return inner
class Thing:
@with_request
def takes_int_str(self, request: Request, x: int, y: str) -> int:
print(request)
return x + 7
thing = Thing()
thing.takes_int_str(1, "A") # Invalid self argument "Thing" to attribute function "takes_int_str" with type "Callable[[str, int, str], int]"
thing.takes_int_str("B", 2) # Argument 2 to "takes_int_str" of "Thing" has incompatible type "int"; expected "str"
Run Code Online (Sandbox Code Playgroud)
两次尝试都会引发 mypy 错误,因为与方法的第一个参数Request不匹配,如上所述。问题是不允许您附加到末尾,所以类似的东西也不起作用。selfConcatenateConcatenateRequestConcatenate[P, Request]
在我看来,这将是执行此操作的理想方法,但它不起作用,因为“Concatenate 的最后一个参数需要是 ParamSpec”。
def with_request(f: Callable[Concatenate[P, Request], R]) -> Callable[P, R]:
...
class Thing:
@with_request
def takes_int_str(self, x: int, y: str, request: Request) -> int:
...
Run Code Online (Sandbox Code Playgroud)
有任何想法吗?
令人惊讶的是,网上对此的了解很少。我可以在Github上找到其他人对此的讨论python/typing,我使用你的示例进行了提炼。
这个解决方案的关键是CallbackProtocol,它在功能上等同于,但另外还使我们能够像标准方法一样Callable修改返回类型__get__(本质上删除参数)。self
from __future__ import annotations
from typing import Any, Callable, Concatenate, Generic, ParamSpec, Protocol, TypeVar
from requests import Request
P = ParamSpec("P")
R = TypeVar("R", covariant=True)
class Method(Protocol, Generic[P, R]):
def __get__(self, instance: Any, owner: type | None = None) -> Callable[P, R]:
...
def __call__(self_, self: Any, *args: P.args, **kwargs: P.kwargs) -> R:
...
def request_wrapper(f: Callable[Concatenate[Any, Request, P], R]) -> Method[P, R]:
def inner(self, *args: P.args, **kwargs: P.kwargs) -> R:
return f(self, Request(), *args, **kwargs)
return inner
class Thing:
@request_wrapper
def takes_int_str(self, request: Request, x: int, y: str) -> int:
print(request)
return x + 7
thing = Thing()
thing.takes_int_str(1, "a")
Run Code Online (Sandbox Code Playgroud)
由于 @Creris 询问了从 的定义引发的 mypy 错误inner,这是 mypy w/ParamSpec和Callback Protocols中的一个明显错误mypy==0.991,因此这里是一个没有错误的替代实现:
from __future__ import annotations
from typing import Any, Callable, Concatenate, ParamSpec, TypeVar
from requests import Request
P = ParamSpec("P")
R = TypeVar("R", covariant=True)
def request_wrapper(f: Callable[Concatenate[Any, Request, P], R]) -> Callable[Concatenate[Any, P], R]:
def inner(self: Any, *args: P.args, **kwargs: P.kwargs) -> R:
return f(self, Request(), *args, **kwargs)
return inner
class Thing:
@request_wrapper
def takes_int_str(self, request: Request, x: int, y: str) -> int:
print(request)
return x + 7
thing = Thing()
thing.takes_int_str(1, "a")
Run Code Online (Sandbox Code Playgroud)