Search code examples
pythondecorator

What's internal mechanism of decorator in Python


I know that the decorator is a function that takes another function and extends its behavior. In the example below, I previously assume that test() now is effectively equivalent to decorator(test)().

def decorator(func):
    def wrapper(*args, **kwargs):
        ...
        res = func(*args, **kwargs)
        ...
        return res
    return wrapper

@decorator
def test():
    pass

However, after adding a function attribute in the decorator and run both test() and decorator(test)(), the results are different. It seems that in the case of decorator(test)(), the decorator function is indeed ran so that num is reset; when using @decorator instead, the decorator function is not ran as I expected?

def decorator(func):
    decorator.num = 0
    def wrapper(*args, **kwargs):
        ...
        res = func(*args, **kwargs)
        ...
        return res
    return wrapper

@decorator
def test():
    pass
   
def test2():
    pass

decorator.num = 5
test()
print(decorator.num)

decorator.num = 5
decorator(test2)()
print(decorator.num)

---------------------
5
0


Solution

  • Your confusion stems from when the decorator runs. The syntax

    @decorator
    def foo(): ...
    

    is equivalent to

    def foo(): ...
    
    foo = decorator(foo)
    

    That is, immediately after the function is defined, the decorator is called on it, and the result of calling the decorator is assigned back to the original function name. It's called only once, at definition time, not once per function call.

    The same is true of classes. The syntax

    @decorator
    class Foo: ...
    

    is equivalent to

    class Foo: ...
    
    Foo = decorator(Foo)