Decorator execution? - python

Playing with decorator, founds something strange: I have something simple as below (a decorator to register the function to a list:
stored_f = []
def register(f):
stored_f.append(f.__name__)
return f
#register
def say_hello(name):
print(f"Hello {name}")
#register
def ur_awesome(name):
return f"{name}, you are awesome!"
I thought the above code will do nothing (it will print nothing out), because I haven't executed anything, but actually a print command "print(stored_f)" will have a output:
['say_hello', 'ur_awesome']
Meaning the line "stored_f.append(f.__name__)" in register() actually got executed, twice. This is weird to me.
I tried to define a wrapper() inside register like normally and then return wrapper, the issue will go away. But still I don't understand why the above code will partially execute the decorator without calling it.

Importing and running a Python file executes it and adding the decorator with #register executes the decorator.
So, even though your script only consists of the variable declaration, the decorator and two function definitions, when you run it, both those definitions will be executed (defining the functions) and decorator functions on them will be executed, so that the functions are correctly decorated.
Added after your comment: consider this:
stored_f = []
def register(f):
stored_f.append(f.__name__)
print('it runs!')
return f
#register
def say_hello(name):
print(f"Hello {name}")
#register
def ur_awesome(name):
return f"{name}, you are awesome!"
You'll find that, if you run this script, the output is:
it runs!
it runs!
All the code in the decorator runs and you just return the same function it received, so f will work just the same before and after.
Also, if you add say_hello("john") to the end of that script, you'll find the decorator does not run again, only the function runs again.
Also, compare this:
stored_f = []
def register(f):
stored_f.append(f.__name__)
def decorated_f(*args):
print('it runs!')
f(*args)
return decorated_f
#register
def say_hello(name):
print(f"Hello {name}")
say_hello("john")
say_hello("pete")
This results in:
it runs!
Hello john
it runs!
Hello pete
Perhaps that's closer to what you were expecting. Note that the decorator doesn't return the original function, it returns the new one.

Based on Grimar's answer, I did the following, may explain it from another angle, at least to myself:
def do_twice(f):
print('This will be executed anyway')
def wrapper():
f()
f()
return wrapper
#do_twice
def say_hello():
print('This is say hello')
output:
This will be executed anyway
if I run:
say_hello()
output:
This is say hello
This is say hello

Related

Memory management for functions and decorators

My question starts with how decorators work in python. Let's look at the following code:
def decorator(F):
def wrapper():
print("start")
F()
print("end")
return wrapper
def f1():
print("f1")
decorated_f1 = decorator(f1)
decorated_f1()
It prints,
start
f1
end
First of all, as much as I know, python uses lazy evaluation. Therefore, it does not evaluate F(), until it is required (when actually decorated_f1() is called). By then, the scope of argument is F is over (end of the decorator function). I would like to know, what python stores in memory when a function is created and overall the memory management that happens for decorators.
The second part of my question is about the results that I get after running the following codes,
def decorator(F):
def wrapper():
print("start")
F()
print("end")
return wrapper
def f1():
print("f1")
decorated_f1 = decorator(f1)
def f1():
print("new f1")
decorated_new_f1 = decorator(f1)
decorated_f1()
decorated_new_f1()
it results in,
start
f1
end
start
new f1
end
However, the following code
def f1():
print("f1")
F = f1
def wrapper():
print("start")
F()
print("end")
wrapped_f1 = wrapper
def f1():
print("new f1")
F = f1
def wrapper():
print("start")
F()
print("end")
wrapped_new_f1 = wrapper
wrapped_f1()
wrapped_new_f1()
produces,
start
new f1
end
start
new f1
end
This makes me confused, because I thought these two codes should be very similar in output. That is why I need help for clarifying what and how things are stored in memory when functions or decorators are declared in python.
You are correct that the second and third example are quite close in how they work. However there is one key difference. The scope the functions are evaluated in. The scope in this case can be seen as all the variables some place in the code has access too, and where those come from. You might have heard of the global scope, this is the scope all code is evaluated in and all variables that are global are accessible by any part of the code. There are also function scopes. These are the variables defined inside of the function, and other functions won't have access to these variables.
To further understand the difference there is one more think you should know. Functions are a reference type in Python, this means that when you declare one all later uses reference the value rather then directly accessing the value. This is the same as for lists and dictionaries.
Why does this all matter? Well because a decorator stores the function scope to correctly evaluate later. This includes the current value of F in this function
def decorator(F):
def wrapper():
print("start")
F()
print("end")
return wrapper
However, if you don't pass F as an argument then the decorator will store the function as a reference to the global function. Thus when you chance F in the third example the F() in wrapped_f1 also changes. This explains the output you see.
Lastly, as a tip. There is a specific syntax for using decorators on functions, and it might make using them easier. Usually a decorator would look like this.
def decorator(F):
def wrapper():
print("start decorating")
F()
print("End decorating")
return wrapper
def undecorated_func():
print("Hellor world!")
#decorator
def decorated_func():
print("Hello World!")
undecorated_func()
decorated_func()
Then the output is
Hello world!
Start decorating
Hello World!
End decorating

Curious as to what the interpreter is doing here

I was working through a decorator design pattern tutorial
(credit to Jungwoo Ryoo)
I'm curious as to why I can swap the lines: return decorator
and print(hello_world()) with return decorator() and print(hello_world)
from functools import wraps
def make_blink(function):
"""Defines the decorator"""
#wraps(function)
# Define the inner function
def decorator():
# Grab the return value of the function being decorated
ret = function()
# Add new functionality to the function being decorated
return "<blink>"+ ret + "<b/link>"
return decorator #return decorator()#<THIS LINE HERE SWAPPED
# Apply the decorator here!
#make_blink
def hello_world():
"""Original function! """
return "Hello, World!"
# Check the result of decorating
print(hello_world()) #print(hello_world) #<THIS LINE HERE SWAPPED
Wouldn't the interpreter be doing something different each time? I'm just looking for some insight to have a better understanding of what's going on
Decorators are just functions really, and functions are just objects.
The lines
#make_blink
def hello_world():
# ...
are essentially the same as
def hello_world():
# ...
hello_world = make_blink(hello_world)
except the function object is never assigned to hello_world first (it's on the stack for to be passed to the decorator).
So whatever you return from make_blink() is assigned back to hello_world. That can be a function object, but it can also be something entirely different.
So when you use return decorator, you tell Python to set hello_world to the nested function object. When you use return decorator(), you tell Python to use the result of the decorator() function. Here, that's a string value. It's as if you did this:
def hello_world():
"""Original function! """
return "Hello, World!"
hello_world = "<blink>" + hello_world() + "</blink>"
And that is fine for this specific example, because body of the hello_world() function only ever returns the same string each time.
But what if you changed the original hello_world() function body to return something different each time you called it? What if you had:
import random
#make_blink
def random_greeting():
return 'Hello ' + random.choice('DonAr', 'Martijn Pieters', 'Guido van Rossum') + '!'
Now it makes a big difference what you return from the make_blink() call! For the top-level of a module, decorators are executed only once, when importing. If you used return decorator() you'd run random.choice() just once, and you have fixed the value of random_greeting to a single, static string result.
Generally speaking, decorators are expected to return a callable object again. That can be the original function (where the decorator just updates some kind of registration), a wrapper function (which does extra things before or after calling the original), or even something different entirely. But that's not set in stone anywhere, and the interpreter doesn't care either way.
Decorators are just reusable things to use in your program, a tool. If you have a specific use for a decorator that returns the result of the original function, then you are free to do so.

Python decorator function execution

I have below decorator demonstration code. If I execute it without explicitly calling greet function, it is executing print statement inside decorator function and outputs Inside decorator.
I am unable to understand this behavior of decorator. How the time_decorator is called even if I didn't call greet function?
I am using Python 3.
def time_decorator(original_func):
print('Inside decorator')
def wrapper(*args, **kwargs):
start = time.clock()
result = original_func(*args, **kwargs)
end = time.clock()
print('{0} is executed in {1}'.format(original_func.__name__, end-start))
return result
return wrapper
#time_decorator
def greet(name):
return 'Hello {0}'.format(name)
Decorators are called at start time (when the python interpreter reads the code as the program starts), not at runtime (when the decorated function is actually called).
At runtime, it is the wrapped function wrapper which is called and which itself calls the decorated function and returns its result.
So this is totally normal that the print line gets executed.
If, i.e, you decorate 10 functions, you will see 10 times the print output. No need to even call the decorated functions for this to happen.
Move the print inside wrapper and this won't happen anymore.
Decorators as well as metaclasses are part of what is called meta-programming (modify / create code, from existing code). This is a really fascinating aspect of programming which takes time to understand but offers amazing possibilities.
time_decorator is executed during function decoration. wrapper is not (this is function invoked when decorated greet() is called).
# is just syntactic sugar. Following code snippets are equivalent.
Decorator syntax:
#time_decorator
def greet(name):
return 'Hello {0}'.format(name)
Explicit decoration process - decorator is a function that returns new function based on another one.
def greet(name):
return 'Hello {0}'.format(name)
greet = time_decorator(greet)

#decorators in Python: why the inner defined function?

I'm just starting with Python and I have just been exposed to decorators. I wrote the following code, mimicking what I am seeing, and it works:
def decorator_function(passed_function):
def inner_decorator():
print('this happens before')
passed_function()
print('this happens after')
return inner_decorator
#decorator_function
def what_we_call():
print('The actual function we called.')
what_we_call()
But then I wrote this, which throws errors:
def decorator_function(passed_function):
print('this happens before')
passed_function()
print('this happens after')
#decorator_function
def what_we_call():
print('The actual function we called.')
what_we_call()
So, why do we need to have that inner nested function inside the decorator function? what purpose does it serve? Wouldn't it be simpler to just use the syntax of the second? What am I not getting?
The funny thing is that BOTH have the same (correct) output, but the second on has error text as well, saying "TypeError: 'NoneType' object is not callable"
Please use language and examples suitable for someone just starting with Python, his first programming language - and also new to OOP as well! :) Thanks.
The reason is that when you wrap what_we_call in decorator_function by doing:
#decorator_function
def what_we_call():
...
What you're doing is:
what_we_call = decorator_function(what_we_call)
In you first example it works because you don't run the inner_function actually, you only initialise it, and then you return the new inner_function back (that you will call later when call the decorated what_we_call):
def decorator_function(passed_function):
def inner_decorator():
print('this happens before')
passed_function()
print('this happens after')
return inner_decorator
Contrarily, in your second example you're going to run 2 print statements and the passed_function (what_we_call in our case) in the between:
def decorator_function(passed_function):
print('this happens before')
passed_function()
print('this happens after')
In other words, you don't return a function in the example of before:
what_we_call = decorator_function(what_we_call)
You run the code (and you see the output), but then decorator_function returns 'None' to what_we_call (overwriting the original function), and when you call 'None' as if it was a function Python complains.
Python decorators are basically just syntactic sugar. This:
#decorator
def fn(arg1, arg2):
return arg1 + arg2
Becomes this:
def fn(arg1, arg2):
return arg1 + arg2
fn = decorator(fn)
That is, a decorator basically accepts a function as an argument, and returns "something"; that "something" is bound to the name of the decorated function.
In nearly all cases, that "something" should be another function, because it is expected that fn will be a function (and will probably be called as though it is).

Please what does func() mean in python when used inside a function

Please what does func() mean in python when used inside a function,For example in the code below.
def identity_decorator(func):
def wrapper():
func()
return wrapper
func is an argument given to the function identity_decorator().
The expression func() means "call the function assigned to the variable func."
The decorator is taking another function as an argument, and returning a new function (defined as wrapper) which executes the given function func when it is run.
Here is some information about decorators.
I was wondering the same! You can see how it works with the follow example:
def make_pretty(func):
def inner():
print("I got decorated")
func()
return inner
def ordinary():
print("I am ordinary")
pretty = make_pretty(ordinary)
pretty()
Output
I got decorated
I am ordinary
Now when you remove the func() and you try to rerun it:
def make_pretty(func):
def inner():
print("I got decorated")
return inner
def ordinary():
print("I am ordinary")
pretty = make_pretty(ordinary)
pretty()
Output
I got decorated
You see the the decorated function was not called. Please have a look here https://www.programiz.com/python-programming/decorator

Categories