TransWikia.com

How does Python know which argument will be original function in decorators?

Stack Overflow Asked on January 5, 2022

I am new to the more advanced features of Python like decorators.

I am unable to understand how the Python interpreter actually understands where to put the original function object in a decorator.

Lets look at an example: Examples taken from here.

Simple decorator with no arguments:

def call_counter(func):
    def helper(*args, **kwargs):
        helper.calls += 1
        return func(*args, **kwargs)
    helper.calls = 0

    return helper

@call_counter
def succ(x):
    return x + 1

This makes perfect sense if we can assume that the first/only argument to the decorator call_counter(func) is the function object that needs to wrapped ie. in this case succ() function.

But things become inconsistent when you are talking about "decorators with parameters". Look at the example below:

Decorator with one argument:

def greeting(expr): # Shouldn't expr be the function here ? Or at least isn't there suppose to be another parameter.
    def greeting_decorator(func): # How does Python know to pass the function down here ?
        def function_wrapper(x):
            print(expr + ", " + func.__name__ + " returns:")
            func(x)
        return function_wrapper
    return greeting_decorator

@greeting("Hello")
def foo(x):
    print(42)

foo("Hi")

Now we know Python has no concept of data-types, so function parameters give no information about what type of object they will contain.

Am I correct ?

Having said that lets look at the line from the above example:

def greeting(expr):

If for decorators the first argument is the function to be wrapped then by that logic expr should point to foo() right ? Otherwise there should be at least two parameters in greeting(), like:

def greeting(func, expr):

But instead Python can "magically" understand that the inner function needs to be passed the function reference:

def greeting(expr): 
    def greeting_decorator(func): # How is it correctly put one level down ?

The code has no datatypes or type information specified, so how is it that for decorators without arguments the function is passed as the first argument and for decorators with arguments the function is passed to the inner function ?

How can the interpreter detect that ?

What is going on here ?

This seems like "magic" to me.

What happens if I have 5 or 6 levels of nested functions ?

I am pretty sure I am missing something pretty basic here.

Thanks.

One Answer

Python evaluates the expression after the @ and uses the result as the decorator function.

Python calls the __call__ method of the object that is the decorator with the function as argument.

using

@call_counter
def succ(x):
    return x + 1

callcounter is the object looked for __call__ to give the argument func

If you use

@greeting("Hello")
def foo(x):
    print(42)

greeting("Hello") is evaluated and its result is an object that Python uses the __call__ method with the func argument.

Answered by rioV8 on January 5, 2022

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP