0

I have built a series of functions and a decorator as follows:

def check_param(func):
    def wrapper(param):
        # check the param
        check(param)
        # then run the function
        return func(param)
    return wrapper

@check_param
def func_a(param='a'):
    print(param)

@check_param
def func_b(param='b'):
    print(param)

The parameter param of the functions has different default values, and I need to check them before running print(param). However, sometimes the functions use default parameter, then how can I check the value of the parameter used in the functions before running it.

Update

I know the package inspect can get the default values of the parameters. However, if I wrap the function with a decorator, inspect only can get the default settings of the decorator (like the decorator check_param) rather than the function func_a. So it cannot solve my problem.

david
  • 842
  • 2
  • 8
  • 25
  • Does this answer your question? [Get a function argument's default value?](https://stackoverflow.com/questions/12627118/get-a-function-arguments-default-value) – SuperStormer Jun 21 '22 at 02:12
  • @SuperStormer, thanks, but it doesn't work because if I use a decorator `checkparam` with the function `func_a`, `inspect` or `func_a.__defaults__` return the setting of the decorator `check_param` rather than `func_a` (the output is `param` with `None` as the default value, but what I want is `param` with `"a"` as the default value). – david Jun 21 '22 at 02:36
  • 1
    You never return the wrapper. This replaces all your functions with None – Mad Physicist Jun 21 '22 at 03:15
  • 1
    "However, if I wrap the function with a decorator, inspect only can get the default settings of the decorator (like the decorator check_param) rather than the function func_a" Did you try using it *inside the code for the decorator*? You know, where you have access to the variable `func`, which stores the original function, and not the decorator (nor a decorated version of the function)? How exactly *did* you try to use it? What you describe should only happen if you try to use `inspect` *on the decorated function after the fact*, but that defeats the purpose of using the decorator anyway. – Karl Knechtel Jun 21 '22 at 03:17

1 Answers1

2

The default arguments for a function are stored in its __defaults__ and __kwdefaults__ attributes (see here: https://docs.python.org/3/reference/datamodel.html). Both are writable, so you can copy them over like this:

def check_param(func):
    def wrapper(param):
        # check the param
        check(param)
        # then run the function
        func(param)
    wrapper.__defaults__ = func.__defaults__
    wrapper.__kwdefaults__ = func.__kwdefaults__
    return wrapper

You can do it even cleaner using functools.wraps, but the default assigned will need to be updated with the attributes you want in addition to the usual WRAPPER_ASSIGNMENTS:

from functools import wraps, WRAPPER_ASSIGNMENTS

def check_param(func):
    @wraps(func, assigned=WRAPPER_ASSIGNMENTS + ('__defaults__', '__kwdefaults__'))
    def wrapper(param):
        # check the param
        check(param)
        # then run the function
        func(param)
    return wrapper
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
  • Note, assigning `__defaults__` and `__kwdefaults__` will not do anything useful for most decorators. For most decorators, where the wrapper's signature looks something like `def wrapper(*args, **kwargs): ...` instead of matching the original function's signature, any `__defaults__` and `__kwdefaults__` you set on the wrapper will be ignored. The wrapper will never use them, and the original function will use its original defaults. – user2357112 Jun 22 '22 at 02:15
  • @user2357112. Correct, which is why those attributes don't appear in `WRAPPER_ASSIGNMENTS` by default. But OP's case is special in that they will actually do exactly what is wanted and expected. You can actually ignore `__kwdefaults__` entirely in this case. – Mad Physicist Jun 22 '22 at 04:48