[英]Python Typing: Copy `**kwargs` from on function to another
It is common pattern in Python extend functions and use **kwargs
to pass all keyword arguments to the extended function.这是 Python 扩展函数中的常见模式,并使用
**kwargs
将所有关键字 arguments 传递给扩展的 function。
ie take即采取
class A:
def bar(self, *, a: int, b: str, c: float) -> str:
return f"{a}_{b}_{c}"
class B:
def bar(self, **kwargs):
return f"NEW_{super().bar(**kwargs)}"
def base_function(*, a: int, b: str, c: float) -> str:
return f"{a}_{b}_{c}"
def extension(**kwargs):
return f"NEW_{super().bar(**kwargs)}"
Now calling extension(no_existing="a")
would lead to a TypeError
, that could be detected by static type checkers.现在调用
extension(no_existing="a")
会导致TypeError
,这可以被 static 类型检查器检测到。
How can I annotate my extension
in order to detect this problem before I run my code?在运行代码之前,如何注释我的
extension
以检测此问题?
This annotation would be also helpful for IDE's to give me the correct suggestions for extension
.此注释也有助于 IDE 为我提供正确的
extension
建议。
PEP 612 introduced the ParamSpec
(see Documentation ) Type. PEP 612引入了
ParamSpec
(参见 文档)类型。
We can exploit this to generate a decorator that tells our type checker, that the decorated functions has the same arguments as the given function:我们可以利用它来生成一个装饰器,它告诉我们的类型检查器,装饰函数与给定的 function 具有相同的 arguments:
from typing import Callable, ParamSpec, TypeVar, cast, Any, Type
# Our test function
def source_func(*, foo: str, bar: int) -> str:
return f"{foo}_{bar}"
# Define some specification, see documentation
P = ParamSpec("P")
T = TypeVar("T")
# For a help about decorator with parameters see
# https://stackoverflow.com/questions/5929107/decorators-with-parameters
def copy_kwargs(kwargs_call: Callable[P, Any], target: Type[T]) -> Callable[[Callable], Callable[P, T]]:
"""Decorator does nothing but returning the casted original function"""
def return_func(func: Callable[..., T]) -> Callable[P, T]:
return cast(Callable[P, T], func)
return return_func
@copy_kwargs(source_func, float)
def kwargs_test(**kwargs) -> float:
print(source_func(**kwargs))
return 1.2
# define some expected return values
okay: float
broken_kwargs: float
broken_return: str
okay = kwargs_test(foo="a", bar=1)
broken_kwargs = kwargs_test(foo=1, bar="2")
broken_return = kwargs_test(foo="a", bar=1)
Checking this file with pyre gives the correct warnings:使用pyre检查此文件会给出正确的警告:
ƛ Found 3 type errors!
src/kwargs.py:30:28 Incompatible parameter type [6]: In anonymous call, for 1st parameter `foo` expected `str` but got `int`.
src/kwargs.py:30:35 Incompatible parameter type [6]: In anonymous call, for 2nd parameter `bar` expected `int` but got `str`.
src/kwargs.py:31:0 Incompatible variable type [9]: broken_return is declared to have type `str` but is used as type `float`.
MyPy just recently (7th April 2022) merged a first implementation for ParamSpec that I did not check yet. MyPy最近(2022 年 4 月 7 日)合并了我尚未检查的 ParamSpec 的第一个实现。
According to the related typedshed Issue, PyCharm should support ParamSpec
but did not correctly detect the copied **kwargs
but complained that okay = kwargs_test(foo="a", bar=1)
would have invalid arguments.根据相关的 typedshed Issue,PyCharm 应该支持
ParamSpec
但没有正确检测到复制的**kwargs
而是抱怨okay = kwargs_test(foo="a", bar=1)
会无效 arguments。
Related Issues:相关问题:
Based on @kound answer.基于@kound 的回答。
To remain DRY, we can do the same without re-declaring return type.为了保持 DRY,我们可以在不重新声明返回类型的情况下做同样的事情。 Type variable
T
will be deduced later (not when copy_kwargs
is called, but when its returned function is), but it doesn't affect further type checking.稍后会推导类型变量
T
(不是在调用copy_kwargs
时,而是在其返回的 function 时),但它不影响进一步的类型检查。
from typing import Callable, ParamSpec, TypeVar, cast, Any
# Our test function
def source_func(*, foo: str, bar: int) -> str:
return f"{foo}_{bar}"
# Define some specification, see documentation
P = ParamSpec("P")
T = TypeVar("T")
# For a help about decorator with parameters see
# https://stackoverflow.com/questions/5929107/decorators-with-parameters
def copy_kwargs(kwargs_call: Callable[P, Any]) -> Callable[[Callable[..., T]], Callable[P, T]]:
"""Decorator does nothing but returning the casted original function"""
def return_func(func: Callable[..., T]) -> Callable[P, T]:
return cast(Callable[P, T], func)
return return_func
@copy_kwargs(source_func)
def kwargs_test(**kwargs) -> float:
print(source_func(**kwargs))
return 1.2
reveal_type(kwargs_test(foo="a", bar=1))
reveal_type(kwargs_test(foo=1, bar="2"))
And here's mypy playground link to look at this in action.这是mypy playground 链接,可以实际查看它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.