0

I'm trying to get my head around Decorators in Python. I've got a handle on what they are for from various answers [ex] on SO: to run code before and/or after a decorated function without modifying the function itself. That said, I'm having trouble understanding what it means to return a function object.

What is the difference between returning the return value of a function and returning the function object? I get that a reference to the function itself is returned, but what is the utility of this behavior?

I didn't use the @Decorator syntax below as I'm still learning that.

For example:

def some_func():
    print('returning 1')
    return 1
def Decorator(func):
    def Wrapper():
        print('doing something first')
        val = func()
        print('doing something after')
        return val
    return Wrapper
Decorated_func = Decorator(some_func)
Decorated_func()

Stepping through this in PythonTutor: calling Decorated_func() shows that Wrapper has a return value of 1 and Decorator has a return value of Wrapper. Does that mean that Decorator actually has a return value of 1 when Decorated_func() is called? I would have thought that the syntax for that behavior would be (within Decorator) return Wrapper(). What is the point of returning a function object?

Nate
  • 11
  • 2
  • A function is just an object, like a list or an int. It serves the same purpose as returning any other object from a function. – juanpa.arrivillaga Apr 16 '20 at 22:36
  • 1
    It seems that you're really asking "Why are higher order functions useful?" – Brian61354270 Apr 16 '20 at 22:37
  • 1
    If you return `Wrapper()` then `Decorated_func == 1` and it will not be a function... try doing that and then see what happens when you try to use `Decorated_func` as a function – juanpa.arrivillaga Apr 16 '20 at 22:37
  • 1
    @Brian yes, and the OP even provides an example of where it would be useful. – juanpa.arrivillaga Apr 16 '20 at 22:37
  • And **no**, that does **not mean** that `Decorator` actually has a return value of `1`, is has a return value of the wrapper function you created inside `Decorator` – juanpa.arrivillaga Apr 16 '20 at 22:38
  • Don’t be distracted by the complexity of implementing decorators - a function object Returned by a decorator can be called with parameters and return results derived from those parameters, whereas a value can’t because it’s, err, a value. – DisappointedByUnaccountableMod Apr 16 '20 at 22:39
  • @barny a function *is a value*. there is no distinction between "values and functions", functions are first-class objects, just like any other value. – juanpa.arrivillaga Apr 16 '20 at 22:41
  • Yeah but in the OP’s example a value might be an integer, or a string, or a list - a function object is callable with parameters – DisappointedByUnaccountableMod Apr 16 '20 at 22:41
  • Thank you all. Quite illuminating, it's making more sense now. @Brian, yes it seems that is my actual question! I'll keep reading. If I'm getting it, returning a function allows you to call the function *with parameters*. – Nate Apr 16 '20 at 22:47

3 Answers3

1

When you return a function, the caller can call it multiple times with different arguments, and it can calculate a different result each time.

If you return the result of calling the function, you're just returning that one value, it will never change. And since it's not a function, you can't call it with different arguments.

Here's a simpler example:

def adder(x):
    def add_x(y):
        return x + y
    return add_x

add_1 = adder(1)
add_3 = adder(3)
print(add_1(10)) # prints 11
print(add_1(20)) # prints 21
print(add_3(10)) # prints 13

If you changed it to something like return add_x(5) then adder(1) would simply return 6, not a function that you can call with different arguments.

Barmar
  • 741,623
  • 53
  • 500
  • 612
1

First, let's clarify your nice, focused question:

Does that mean that Decorator actually has a return value of 1 when Decorated_func() is called?

No. Decorator is a configuration tool. You feed it a function object. Decorator inserts that function object into its hidden template (Wrapper), and then returns that customized Wrapper as a new function. That customized function is the return value of Decorator.

Calling that wrapper does not alter how Decorator operates. Note that this is not a call to Decorator. Decorator made a customized version of wrapper -- that is what has a value of 1. Decorator is still its original self, a customization factory waiting for someone to insert some other function.

Prune
  • 76,765
  • 14
  • 60
  • 81
0

Returning a function allows you a "call in 2 steps". For example, from the same base function, you could create as many functions as you want.

def testDecorator(x):
    print(f'I am {x}')
    return x * 2

def printA(func):
    def wrapper(x):
       print('I am the decorator A opening')
       result = func(x)
       print('I am the decorator A ending')
       return result
    return wrapper

def printB(func):
    def wrapper(x):
       print('I am the decorator B opening')
       result = func(x)
       print('I am the decorator B ending')
       return result
    return wrapper

testA = printA(testDecorator)
testB = printB(testDecorator)
>>> testA(5)
I am the decorator A opening
I am 5
I am the decorator A ending
10
>>> testB(5)
I am the decorator B opening
I am 5
I am the decorator B ending
10
Shizzen83
  • 3,325
  • 3
  • 12
  • 32