user8491711
user8491711

Reputation: 91

Python decorator with arbitrary positioned parameter of decorated function

What would be the way for python decorator to become "polymorphic"? What I mean is it seeks for one exact parameter of decorated function, does something to/with it, then passes the modified parameter to the function. However, this parameter with common name has arbitrary position in any decorated function.

def decorator(func):
    def wrapper(exactly_this_param, *args):
        new = do_smth(exactly_this_param)
        return func(exactly_this_param=new, *args)
    return wrapper

@decorator
def func1(a, b, exactly_this_param, c):
    # does something

@decorator
def func2(exactly_this_param):
    # does something

@decorator
def func3(a, b, c, d, e, exactly_this_param, *args, **kwargs):
    # does something

Upvotes: 1

Views: 90

Answers (1)

klaas
klaas

Reputation: 1811

With keyword arguments (kwargs) this would be easy since *kwargs gives you the names and values of keyword arguments inside the decorator. So you would receive something like this:

func3(1,2,3,4,5,6,7,test="hallo")

would give you:

kwargs{'test': 'hallo'}

So you could just search for "exactly_this_param" and change it ..

BUT you chose to use normal args. This is one step more but also possible.

So here is a possible solution: (I used python 3)

With "non keyword arguments" (args) you can use the combination of:

  • args -> gives you a tuple of the actual values of this func call
  • func.code.co_varnames -> gives you the argument names as in the function definition

I assigned them to the varaible func_params

func_params = func.__code__.co_varnames

So you can look for the index of "exactly_this_param" in func.code.co_varnames and change the value in args at exactly this index as well. This is this block:

new_args[func_params.index("exactly_this_param")]=999

You also have to convert args to a list (new_args) and back to a tuple, since tuples are immutable in python.

This results in something like this (example code - but running):

def decorator(func):
def wrapped(*args, **kwargs):
    # a tuple of the names of the parameters that func accepts
    print("    ... decorator magic ....")
    print("    .... args" + str(args))
    print("    .... kwargs" + str(kwargs))
    func_params = func.__code__.co_varnames
    print("    .... param_names:" + str(func_params))
    # grab all of the kwargs that are not accepted by func
    new_args=list(args)
    new_args[func_params.index("exactly_this_param")]=999
    args=tuple(new_args)
    print("    .... new_args" + str(args))
    return func(*args, **kwargs)
return wrapped

@decorator
def func1(a, b, exactly_this_param, c):
    # does something
    print("func1 => exactly_this_slightly_different_param: " + str(exactly_this_param))

@decorator
def func2(exactly_this_param):
    # does something
    print("func2 =>  exactly_this_slightly_different_param: " + str(exactly_this_param))

@decorator
def func3(a, b, c, d, e, exactly_this_param, *args, **kwargs):
    # does something
    print("func3 =>  exactly_this_slightly_different_param: " + str(exactly_this_param))

if __name__ == "__main__":
    func1(1,2,3,4)
    func2(12)
    func3(1,2,3,4,5,6,7,test="hallo")

The resulting output shows that the paramter is always found and changed to 999.

c:\khz\devel>python test_dec.py
    ... decorator magic ....
    .... args(1, 2, 3, 4)
    .... kwargs{}
    .... param_names:('a', 'b', 'exactly_this_param', 'c')
    .... new_args(1, 2, 999, 4)
func1 => exactly_this_slightly_different_param: 999
    ... decorator magic ....
    .... args(12,)
    .... kwargs{}
    .... param_names:('exactly_this_param',)
    .... new_args(999,)
func2 =>  exactly_this_slightly_different_param: 999
    ... decorator magic ....
    .... args(1, 2, 3, 4, 5, 6, 7)
    .... kwargs{'test': 'hallo'}
    .... param_names:('a', 'b', 'c', 'd', 'e', 'exactly_this_param', 'args', 'kwargs')
    .... new_args(1, 2, 3, 4, 5, 999, 7)
func3 =>  exactly_this_slightly_different_param: 999

Answer based on this SO question.

Upvotes: 1

Related Questions