For the next post in my syntactic sugar series, I thought I would tackle decorators.
Let&aposs look at a simple example of a function that has a single decorator applied to it.
@decorator
def func():
...
Decorator example
If you&aposre familiar with decorators then your mental model for this code may be that the code passes func
to decorator
and assigns the return value back to func
:
def func():
...
func = decorator(func)
Reasonable mental model for the unravelling of the decorator example
Seems entirely reasonable to me! But does the bytecode match that?
2 0 LOAD_GLOBAL 0 (decorator)
3 2 LOAD_CONST 1 (<code object func at 0x100764240, file "<stdin>", line 2>)
4 LOAD_CONST 2 (&aposspam.<locals>.func&apos)
6 MAKE_FUNCTION 0
8 CALL_FUNCTION 1
10 STORE_FAST 0 (func)
Bytecode for the decorator example
It turns out there&aposs a slight "cheat" that the bytecode gets to take which we didn&apost use in our mental model. Here&aposs what the bytecode is doing:
decorator
gets pushed on to the stack.- The stack is
[decorator]
- The (combined) namespace is
{"decorator": decorator}
- The stack is
- The code object for
func
gets pushed on to the stack.[decorator, code-object-of-func]
{"decorator": decorator}
- A function object is created from the code object.
[decorator, function-object-of-func]
{"decorator": decorator}
decorator
is called.[return-value-of-decorator]
{"decorator": decorator}
func
gets assigned the value thatdecorator
returned.[]
{"decorator": decorator, "func": return-value-of-decorator}
Notice how not until the very end is anything actually assigned to the name func
? Because the execution stack can hold values without them being assigned to a name, Python is able to avoid assigning the final value to func
until the very end after all decorators are evaluated. This is specifically mentioned in the language definition:
... the original function is not temporarily bound to the name func
.
My assumption is this is to protect users from accidentally referring to the name before the final object that is to be func
is created and available.
To replicate this we need to assign the function to a temporary name that is not referenced anywhere else to avoid the function&aposs actual name from being exposed too early. You might think to initially define the function with a temporary name, but you want the function object to be accurate and a code object&aposs co_name
attribute is read-only and so we can&apost patch things up appropriately later. What all of this means is we need to:
- Create
func
. - Give it a temporary name that isn&apost referenced anywhere.
- Delete the
func
name. - Apply all the decorators.
- Assign the resulting object to
func
.
This results in the code unravelling to:
def func():
...
_temp_func_name = func
del func
func = decorator(_temp_func_name)
Unravelling of decoratorsfrom Planet Python
via read more
No comments:
Post a Comment