Convert function arguments with a decorator

Question:

I want to convert some function arguments with a decorator like this:

def check_args(*args1):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(len(args)):
                if type(args[i]) == args1[i]:
                    args[i] = args1[i](args[i])
            result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@check_args(str, str)
def function(arg1, arg2):
    pass

But when I run it I get this error:

    args[i] = args1[i](args[i ]) 
TypeError: 'tuple' object does not support item assignment

args[i] is not a tuple, it’s an int and that code should translate to 5 = str(5)

Can you help me figure this out or another way to convert the arguments, but still with a decorator?

Asked By: StevieG25

||

Answers:

So you meant to convert each argument which is the wrong type, but you will have to convert the arguments from a tuple to a list first:

def check_args(*args1):
    def decorator(func):
        def wrapper(*args, **kwargs):
            args = list(args)         # convert args to a list here
            for i in range(len(args)):
                if type(args[i]) != args1[i]:
                    args[i] = args1[i](args[i])
            result = func(*args, **kwargs)  # *args works for both tuples and lists
            return result
        return wrapper
    return decorator

@check_args(str, str)
def function(arg1, arg2):
    pass
Answered By: quamrana

Python 3.10.5

from typing import ParamSpec, TypeVar, Any, overload
from collections.abc import Callable, Awaitable; from collections import OrderedDict
from types import MappingProxyType
import functools; import inspect

P = ParamSpec("P")
R = TypeVar("R")
ARGUMENT = TypeVar("ARGUMENT", bound=Any)
ANNOTATION = TypeVar("ANNOTATION", bound=type)

@overload
def dynamic_cast(func_: Callable[P, R], *, strict: bool = ..., cast_impl: Callable[[Any, type], Any] | None = None)
    -> Callable[..., R]: ...

@overload
def dynamic_cast(func_: None = None, *, strict: bool = ..., cast_impl: Callable[[Any, type], Any] | None = None)
    -> Callable[[Callable[P, R]], Callable[..., R]]: ...

def dynamic_cast(func_: Callable[P, R] | None = None, *, strict: bool = False, cast_impl: Callable[[Any, type], Any] | None = None)
    -> Callable[..., R] | Callable[[Callable[P, R]], Callable[..., R]]:
    if strict: raise NotImplementedError
    def dynamic_cast_impl_(argument: ARGUMENT, annotation: ANNOTATION) -> ANNOTATION:
        if isinstance(argument, str) and annotation is int:
            return annotation(float(argument))
        elif annotation in (inspect.Parameter.empty, inspect.Signature.empty):
            return argument
        else:
            return annotation(argument)
    def decorator_dynamic_cast(func: Callable[P, R]) -> Callable[..., R]:
        @functools.wraps(func)
        def wrapper_dynamic_cast(*args: Any, **kwargs: Any) -> R:
            cast_impl_ = cast_impl or dynamic_cast_impl_
            signature: inspect.Signature = inspect.signature(func)
            parameters: MappingProxyType[str, inspect.Parameter] = signature.parameters
            bind: inspect.BoundArguments = signature.bind(*args, **kwargs)
            arguments: OrderedDict[str, Any] = bind.arguments
            args_f = list(); kwargs_f = dict()
            for pname, argument in arguments.items():
                parameter: inspect.Parameter = parameters[pname]
                annotation: type = parameter.annotation
                argument = cast_impl_(argument, annotation)
                match parameter.kind:
                    case inspect.Parameter.POSITIONAL_ONLY | inspect.Parameter.POSITIONAL_OR_KEYWORD:
                        args_f.append(argument)
                    case inspect.Parameter.VAR_POSITIONAL:
                        args_f.extend(argument)
                    case inspect.Parameter.KEYWORD_ONLY | inspect.Parameter.VAR_KEYWORD:
                        kwargs_f.update({pname: argument})
            result = func(*args_f, **kwargs_f)
            return cast_impl_(result, signature.return_annotation)
        return wrapper_dynamic_cast
    if func_ is None:
        return decorator_dynamic_cast
    else:
        return decorator_dynamic_cast(func_)

@overload
def async_cast(func_: Callable[P, Awaitable[R]], *, strict: bool = ..., cast_impl: Callable[[Any, type], Awaitable[Any]] | None = None)
    -> Callable[..., Awaitable[R]]: ...

@overload
def async_cast(func_: None = None, *, strict: bool = ..., cast_impl: Callable[[Any, type], Awaitable[Any]] | None = None)
    -> Callable[[Callable[P, Awaitable[R]]], Callable[..., Awaitable[R]]]: ...

def async_cast(func_: Callable[P, Awaitable[R]] | None = None, *, strict: bool = False, cast_impl: Callable[[Any, type], Awaitable[Any]] | None = None)
    -> Callable[..., Awaitable[R]] | Callable[[Callable[P, Awaitable[R]]], Callable[..., Awaitable[R]]]:
    if strict: raise NotImplementedError
    async def async_dynamic_cast_impl_(argument: ARGUMENT, annotation: ANNOTATION) -> Awaitable[ANNOTATION]:
        if isinstance(argument, str) and annotation is int:
            return annotation(float(argument))
        elif annotation in (inspect.Parameter.empty, inspect.Signature.empty):
            return argument
        else:
            return annotation(argument)
    def decorator_async_cast(func: Callable[P, Awaitable[R]]) -> Callable[..., Awaitable[R]]:
        async def wrapper_async_cast(*args: Any, **kwargs: Any) -> R:
            cast_impl_ = cast_impl or async_dynamic_cast_impl_
            signature: inspect.Signature = inspect.signature(func)
            parameters: MappingProxyType[str, inspect.Parameter] = signature.parameters
            bind: inspect.BoundArguments = signature.bind(*args, **kwargs)
            arguments: OrderedDict[str, Any] = bind.arguments
            args_f = list(); kwargs_f = dict()
            for pname, argument in arguments.items():
                parameter: inspect.Parameter = parameters[pname]
                annotation: type = parameter.annotation
                argument = await cast_impl_(argument, annotation)
                match parameter.kind:
                    case inspect.Parameter.POSITIONAL_ONLY | inspect.Parameter.POSITIONAL_OR_KEYWORD:
                        args_f.append(argument)
                    case inspect.Parameter.VAR_POSITIONAL:
                        args_f.extend(argument)
                    case inspect.Parameter.KEYWORD_ONLY | inspect.Parameter.VAR_KEYWORD:
                        kwargs_f.update({pname: argument})
            return await cast_impl_(await func(*args_f, **kwargs_f), signature.return_annotation)
        return wrapper_async_cast
    if func_ is None:
        return decorator_async_cast
    else:
        return decorator_async_cast(func_)

example:

import asyncio

@dynamic_cast
def sum(a: int, b: int) -> int:
    return a + b

@async_cast
async def async_sum(a: int, b: int) -> int:
    return a + b

@dynamic_cast
def add_one(x: float) -> float:
    return x + 1.01

assert sum(1, 2) == 3
assert sum("3", "4") == 7
assert add_one("1.0") == 2.01
assert asyncio.run(async_sum(1, 2)) == 3
Answered By: SPapiernik
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.