Search code examples
pythonpython-decorators

Python decorator not acting as expected


Just playing with decorators, and have a simple example I made. I was expecting that every time I called a method, the method name would be added to the list.

python_func_calls = []

def log_func_call(func):
    python_func_calls.append(func.__name__)
    return func

@log_func_call
def print_a():
    print('I am the a function...')
    
@log_func_call
def print_b():
    print('I am the b function...')
    
print_a()
print_b()
print_b()
print_a()

print(python_func_calls)

But this gives me the following content of python_func_calls:

['print_a', 'print_b']

I had thought there would be 4 entries in the list, as decorated functions were called 4 times.


Solution

  • The decorator syntax is similar to:

    def print_a():
        ...
    
    print_a = log_func_call(print_a)
    

    Hence, the decorator is called exactly once with the target function as an argument. If you want the decorated version to do extra work for every function call, you need to create a wrapper function inside the decorator that does two things:

    1. Do the extra work: python_func_calls.append(func.__name__)
    2. Invoke the decorated function and return whatever that function returns: return func(*args, **kwargs).

    The following decorator implements these requirements:

    from functools import wraps
    
    python_func_calls = []
    
    def log_func_call(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            python_func_calls.append(func.__name__)
            return func(*args, **kwars)
    
        return wrapper
    

    Note that the decorator uses another decorator wraps which adjusts the metadata of the wrapper function to mimic that of the decorated function func (e.g. signature and docstring).