简体   繁体   中英

How to verify the signature of python method when using Default parameters?

Is there a way to check if all the argument passes to a method matches the expected one? I tried the inspect.signature.bind to match them but it does now work properly when using a default parameter, even when the type of the default value is not same as passed. eg in the last call to the method faa, I send a list as the third argument, but the expected default argument is Bool. Is there a way to match that also?

import inspect
def foo(a, b, x=True):
    pass

def faa(*args, **kwargs):
  try:
    inspect.signature(foo).bind(*args, **kwargs)
  except TypeError:
    print("Does not match")
  else:
    print("Matches")  


faa(1)                     # Does not match , Ok
faa(10, None, 'something') # Matches, Ok.
faa(1, 2)                  # Matches, Ok.
faa(10, None, [1,2])       # Matches, NOK. Is there a way to add a check to not match ?

You could use the decorator validate_arguments belonging to the module pydantic . As an example:

from pydantic import validate_arguments


@validate_arguments
def faa(a, b, x: bool = True):
    pass


faa(
    a=10, b=None, x=[1, 2]
)

Output

pydantic.error_wrappers.ValidationError: 1 validation error for Faa
x
  value could not be parsed to a boolean (type=type_error.bool)

You could define a simple decorator that can be used for every function you want to validate. This decorator builds up on the inpsect module. The idea is to validate the function parameter (both positional and non-positional) against the default value of the corresponding argument in the function.

import inspect
from functools import wraps

def validate_func_args(func):
    def check_type(value, fn_param: inspect.Parameter):
        if not fn_param.default is fn_param.empty:
            _default = fn_param.default
            # We do not type check if the default value is None or if the argument is none
            is_none = _default is None or value is None

            if not is_none and type(value) != type(_default):
                raise TypeError(f"Invalid type encountered for {fn_param.name}")

    @wraps(func)
    def wrapper(*args, **kwargs):
        # First pass
        inspect.signature(foo).bind(*args, **kwargs)

        # Second pass (validate parameters with default values)
        args_count = len(args)
        fn_params = inspect.signature(foo).parameters

        for arg, param in zip(args, list(fn_params)[:args_count]):
            check_type(arg, fn_params[param])

        for kwarg, value in kwargs.items():
            check_type(value, fn_params[kwarg])

        # All good. Now call the function and return the result
        result = func(*args, **kwargs)
        return result

    return wrapper

Function definition

@validate_func_args
def foo(a, b=2, x=True):
    pass

Function call

foo(10, None, "something") 

Output

TypeError: Invalid type encountered for x

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM