用于更改参数类型的装饰器的 Python 3 类型提示

问题描述 投票:0回答:1

我有一个装饰器,它将函数参数强制转换为它们的类型暗示类型:

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
python python-3.x python-decorators type-hinting
1个回答
0
投票

为了解决这个问题,您可以使用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
© www.soinside.com 2019 - 2024. All rights reserved.