'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 1:[1]

The decorator is called at the function definition so twice total:

python_func_calls = []

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

@log_func_call #first call
def print_a():
    print('I am the a function...')
    
@log_func_call #second call
def print_b():
    print('I am the b function...')
    
print_a() #decorator not called
print_b() #decorator not called
print_b() #decorator not called
print_a() #decorator not called

print(python_func_calls)

So despite 4 function calls there are only 2 decorator calls.

Solution 2:[2]

You need to create a new function and return that from the decorator. Inside this function, call the passed in wrapped function. For example:

from functools import wraps

python_func_calls = []

def log_func_call(func):
    @wraps(func)
    def wrapped(*args, **kwargs):
        python_func_calls.append(func.__name__)
        func(*args, **kwargs)
    return wrapped

@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)

This gives python_func_calls as:

['print_a', 'print_b', 'print_b', 'print_a']

The reason this works is that the function returned by the decorator replaces the wrapped function. So it gets called each time, rather than just when the decorator is created.In your original code, you return the original function...so nothing changes when you call the function.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Eli Harold
Solution 2