Your understanding is entirely correct. Decorator syntax is just syntactic sugar, the lines:
@my_decorator
def my_function(arg):
print(arg + 1)
are executed as
def my_function(arg):
print(arg + 1)
my_function = my_decorator(my_function)
without my_function
actually having been set before the decorator is called*.
So my_function
is now bound to the wrapper()
function created in your my_decorator()
function. The original function object was passed into my_decorator()
as the func
argument, so is still available to the wrapper()
function, as a closure. So calling func()
calls the original function object.
So when you call the decorated my_function(1)
object, you really call wrapper(1)
. This function receives the 1
via the name func_arg
, and wrapper()
then itself calls func(func_arg)
, which is the original function object. So in the end, the original function is passed 1
too.
You can see this result in the interpreter:
>>> def my_decorator(func):
... def wrapper(func_arg):
... print('Before')
... func(func_arg)
... print('After')
... return wrapper
...
>>> @my_decorator
... def my_function(arg):
... print(arg + 1)
...
>>> my_function
<function my_decorator.<locals>.wrapper at 0x10f278ea0>
>>> my_function.__closure__
(<cell at 0x10ecdf498: function object at 0x10ece9730>,)
>>> my_function.__closure__[0].cell_contents
<function my_function at 0x10ece9730>
>>> my_function.__closure__[0].cell_contents(1)
2
Closures are accessible via the __closure__
attribute, and you can access the current value for a closure via the cell_contents
attribute. Here, that's the original decorated function object.
It is important to note that each time you call my_decorator()
, a new function object is created. They are all named wrapper()
but they are separate objects, each with their own __closure__
.
* Python produces bytecode that creates the function object without assigning it to a name; it lives on the stack instead. The next bytecode instruction then calls the decorator object:
>>> import dis
>>> dis.dis(compile('@my_decorator\ndef my_function(arg):\n print(arg + 1)\n', '', 'exec'))
1 0 LOAD_NAME 0 (my_decorator)
2 LOAD_CONST 0 (<code object my_function at 0x10f25bb70, file "", line 1>)
4 LOAD_CONST 1 ('my_function')
6 MAKE_FUNCTION 0
8 CALL_FUNCTION 1
10 STORE_NAME 1 (my_function)
12 LOAD_CONST 2 (None)
14 RETURN_VALUE
So first LOAD_NAME
looks up the my_decorator
name. Next, the bytecode generated for the function object is loaded as well as the name for the function. MAKE_FUNCTION
creates the function object from those two pieces of information (removing them from the stack) and puts the resulting function object back on. CALL_FUNCTION
then takes the one argument on the stack (it's operand 1
tells it how many positional arguments to take), and calls the next object on the stack (the decorator object loaded). The result of that call is then stored under the name my_function
.