简体   繁体   中英

How to capture arguments of original function after applying 'setattr' to it?

I have a module in a project that loads before anything else and this module needs to add custom logic to a list of functions that I retrieve from database. To add this custom logic I use builtin python function setattr.

Also, people of the same project want to capture arguments for some of the functions that are in the same list. To capture arguments they use inspect.getcallargs function. However, instead of getting arguments of original function they get the arguments of the wrapper function.

I understand why this happens but don't have a solution for the problem.

My decorator/wrapper looks like the following:

def func_decorator(func, some_other_params):
    def func_to_return(*args, **kwargs):
        # some code
        payload = func(*args, **kwargs)
        # some other code
        return payload
    return func_to_return

So, in the function that goes over the list of functions to decorate I have something that looks like the following:

def add_custom_logic_to_funcs:
    ....
    for func_name in funcs:
        func = getattr(func_class, func_name)
        setattr(func_class, func_name, func_decorator(func, some_params))

If let's say original function looks like the following:

class A:
    def(self, x=None, y=None, z=1):
        #some code

Calling inspect.getcallargs results in returnning {'args': (some object at 0x000000001BCA8C50,), 'kwargs': {}} instead of {'self': some object at 0x000000001BCA8C50, 'x': None, 'y': None, 'z': 1}

You will need two things there - First is that your decorator is itself decorated with functools.wraps() , so that some metadata on the decorated function is added to the decorated function:

from functools import decorator

def func_decorator(func, some_other_params):
    @wraps(func)
    def func_to_return(*args, **kwargs):
        # some code
        payload = func(*args, **kwargs)
        # some other code
        return payload
    return func_to_return

That however, don't make inspect.getcallargs to reflect the decorated signature. Instead of that, you have to use inspect.signature - which returns a richer Signature object, but that have a .parameters attribute that will look like the return from .getcallargs :


In [26]: def deco(func): 
    ...:     @wraps(func) 
    ...:     def wrapper(*args, **kw): 
    ...:         return func(*args, **kw) 
    ...:     return wrapper 
    ...:                                                                                                                                                                                       

In [27]: class A: 
    ...:     @deco 
    ...:     def b(self, a, b=2): 
    ...:         pass 
    ...:                                                                                                                                                                                       

In [28]: inspect.signature(A().b).parameters                                                                                                                                                   
Out[28]: mappingproxy({'a': <Parameter "a">, 'b': <Parameter "b=2">})

In [29]: inspect.signature(A.b).parameters                                                                                                                                                     
Out[29]: 
mappingproxy({'self': <Parameter "self">,
              'a': <Parameter "a">,
              'b': <Parameter "b=2">})

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