我有一个装饰器,它将函数参数强制转换为它们的类型暗示类型:
import inspect
from functools import wraps
from typing import Any, Callable, TypeVar
R = TypeVar("R")
def coerce_arguments(func: Callable[..., R]) -> Callable[..., R]:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> R:
signature = inspect.signature(func)
bound_args = signature.bind(*args, **kwargs)
bound_args.apply_defaults()
new_args = []
for name, param in signature.parameters.items():
if name in bound_args.arguments:
value = bound_args.arguments[name]
if param.annotation != inspect.Parameter.empty:
try:
coerced_value = param.annotation(value)
except Exception:
# attempt to run the function un-coerced
coerced_value = value
bound_args.arguments[name] = coerced_value
new_args.append(bound_args.arguments[name])
return func(*new_args, **kwargs)
return wrapper
@coerce_arguments
def test(x: int) -> int:
return x + 1
test("1")
# >>> 2
这工作正常,但现在我的类型检查器将允许我使用任意数量的参数调用
test
((...) -> int
) - 不是很有帮助。相反,仍应使用相同数量的参数来调用该函数,但所有这些参数都使用 Any 类型。 IE。 (x: Any) -> int
为 test
。
我尝试使用
typing.ParamSpec
但没有成功:(
import inspect
from functools import wraps
from typing import Any, Callable, TypeVar, ParamSpec
R = TypeVar("R")
P = ParamSpec("P")
O = ParamSpec("O")
def coerce_arguments(func: Callable[P, R]) -> Callable[O, R]:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> R: # Not sure what types for args and kwargs
...
new_args = [] # type hint new_args?
...
return func(*new_args, **kwargs)
return wrapper
@coerce_arguments
def test(x: int) -> int:
return x + 1
# (**O@coerce_arguments) -> int
为了解决这个问题,您可以使用typing.get_type_hints来获取函数参数的类型提示,然后仅当参数具有具体类型提示时才应用强制转换。通过文档
import inspect
from functools import wraps
from typing import Any, Callable, TypeVar, get_type_hints
R = TypeVar("R")
P = TypeVar("P", bound=Callable[..., Any])
O = TypeVar("O", bound=Callable[..., Any])
def coerce_arguments(func: P) -> O:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> R:
# Get the type hints for the function parameters
type_hints = get_type_hints(func)
new_args = []
for name, value in zip(func.__code__.co_varnames, args):
param_hint = type_hints.get(name, Any)
if param_hint != Any:
try:
coerced_value = param_hint(value)
except (TypeError, ValueError):
# Failed to coerce, use the original value
coerced_value = value
new_args.append(coerced_value)
else:
new_args.append(value)
return func(*new_args, **kwargs)
return wrapper
@coerce_arguments
def test(x: int) -> int:
return x + 1
test("1") # Output: 2