python: 将子函数参数传递给父函数的kwargs,且IDE静态分析有类型提示的3种方法 (2025年)
方法1: TypedDict+Unpack
强烈推荐,适用于自己编写的库内使用
外部库不推荐,因为要额外维护Kwargs参数类,且无法对应不同版本的外部库
https://docs.python.org/3/library/typing.html#typing.Unpack
副作用,二次继承TypedDict
起码在vscode pylance里没有提示get,update
class T(TypedDict, total=False):
Get: bool
get: int
Update: float
update: str
class TTT(T, total=False):
ttt: str
def t(**kwargs: Unpack[T]):... # 有get, update这2个参数,共4个参数
def ttt(**kwargs: Unpack[TTT]):... # 缺少get, update这2个参数,因为命名与dict.get()/dict.update()冲突
方法2: .pyi存根文件
stubgen -o typings
缺点:
- 需要额外维护.pyi,每次更新源码都需要手动更新.pyi内的函数签名
- 在.py内会优先使用内嵌签名,而非.pyi。
├─ src
│ ├─ app.py # 不使用.pyi,使用.py的上下文签名
│ └─ call_app.py # 默认使用.pyi
├─ typings
│ └─ src
│ └─ app.pyi
- vscode只认工作根目录下的typings,且typings需要有与src相同的文件夹结构
├─ src
│ └─ app.py
├─ typings
│ └─ src
│ └─ app.pyi
├─ requirements.txt
└─ pyproject.toml
方法3: functools.wraps
https://stackoverflow.com/questions/71968447/python-typing-copy-kwargs-from-one-function-to-another
https://github.com/python/typing/issues/270#issuecomment-1346124813
缺点:
- 只能复制,不能在复制的基础上添加自己的参数:https://github.com/python/cpython/issues/107001
- async函数使用wraps后,会丢失被装饰函数的所有信息,如docstring。
使用场景:简单包装外部库的函数;pylance静态分析会正确显示
import functools
from collections.abc import Callable
from typing import Any, Concatenate, ParamSpec, TypeVar, reveal_type
PS = ParamSpec("PS")
TV = TypeVar("TV")
#推荐
def copy_args(
func: Callable[PS, Any]
) -> Callable[[Callable[..., TV]], Callable[PS, TV]]:
"""Decorator does nothing but returning the casted original function"""
def return_func(func: Callable[..., TV]) -> Callable[PS, TV]:
return cast(Callable[PS, TV], func)
return return_func
#不推荐
def copy_callable_signature(
source: Callable[PS, TV]
) -> Callable[[Callable[..., TV]], Callable[PS, TV]]:
"""```python
def f(x: bool, *extra: int) -> str:
return str(...)
# copied signature:
@copy_callable_signature(f)
def test(*args, **kwargs): # type: ignore[no-untyped-def]
return f(*args, **kwargs)
```"""
def wrapper(target: Callable[..., TV]) -> Callable[PS, TV]:
@wraps(source)
def wrapped(*args: PS.args, **kwargs: PS.kwargs) -> TV:
return target(*args, **kwargs)
return wrapped
return wrapper
# 类内有self,使用
def copy_method_signature(
source: Callable[Concatenate[Any, PS], TV]
) -> Callable[[Callable[..., TV]], Callable[Concatenate[Any, PS], TV]]:
"""```python
class A:
def foo(self, x: int, y: int, z: int) -> float:
return float()
class B:
# copied signature:
@copy_method_signature(A.foo)
def bar(self, *args, **kwargs): # type: ignore[no-untyped-def]
print(*args)
```"""
def wrapper(target: Callable[..., TV]) -> Callable[Concatenate[Any, PS], TV]:
@wraps(source)
def wrapped(self: Any, /, *args: PS.args, **kwargs: PS.kwargs) -> TV:
return target(self, *args, **kwargs)
return wrapped
return wrapper
配合kwargs_filter
def kwargs_filter(funcs: List[Union[Callable, object]], kwargs, check=CHECK_KWARGS):
"""Filter out invalid kwargs to prevent Exception
Don't use this if the funcs
actually parse args by `**kwargs`
while using `.pyi` to hint args,
which will filter out your needed kwargs.
```python
def Popen(cmd, Raise, **kwargs):
kwargs = Kwargs([sp.Popen, Popen], kwargs)
p = sp.Popen(cmd, **kwargs)
return p
```
"""
if not check:
return kwargs
from inspect import signature, isclass
d = {}
for f in funcs:
if isclass(f):
params = signature(f.__init__).parameters
elif callable(f):
params = signature(f).parameters
else:
raise TypeError(f"Invalid type: {type(f)}")
# Log.debug(f"{funcs[0]} {params}")
for k, v in kwargs.items():
if k in params:
d[k] = v
else:
Log.warning(f"Invalid kwarg: {k}={v}, please report to developer")
return d
def copy_args(
func: Callable[_PS, Any]
) -> Callable[[Callable[..., _TV]], Callable[_PS, _TV]]:
"""https://dev59.com/NnMOtIcB2Jgan1znX5jv"""
def return_func(func: Callable[..., _TV]) -> Callable[_PS, _TV]:
return cast(Callable[_PS, _TV], func)
return return_func
def copy_args_with(
func: Callable[_PS, Any], prefix: type[_TV2]
) -> Callable[[Callable[..., _TV]], Callable[Concatenate[_TV2, _PS], _TV]]:
"""https://dev59.com/NnMOtIcB2Jgan1znX5jv"""
def return_func(func: Callable[..., _TV]) -> Callable[Concatenate[_TV2, _PS], _TV]:
return cast(Callable[Concatenate[_TV2, _PS], _TV], func)
return return_func

浙公网安备 33010602011771号