For the past week-or-so, I've been teaching myself Python and Flask. I've been all over the place with what I want to really dig deep into and, I hope, I'm not the only beginner who's been making this mistake.
Lately, I've found Python decorators to be a bit confusing. A lot of the YouTube explanations I found tended to be very convoluted and add an extra layer of complexity to something that shouldn't be that complicated. So I'm dedicating this blog post to explain what these decorators do in the simplest way. Minimum effort!
Python functions, and in many other programming languages, are objects. This means they can be passed around and used in your program. One of the places it can be passed to are arguments to other functions.
Consider this block of code:
def divide(a, b): return a / b divide(10, 2) # 5.0
Above we've created a super simple function that accepts two arguments and divides them and returns that value. But what if a user ran
divide(10, 0)? We know it would error out because you can't divide by zero. So let's fix that:
def divide(a, b): if b == 0: return 'You cannot divide by zero' return a / b divide(10, 0) # You cannot divide by zero
If the second number is equal to 0, we will hit our return statement of
You cannot divide by zero and the function will end there. This 100% works but what if, for some reason or another, you didn't want to add that
if statement to your function? I'm sure there are several reasons in which I'm drawing blanks on but... wow what if? Well, this is where decorators come into play.
Python decorators allow us to extend the functionality of our functions without altering the function itself. Let's create our first decorator:
def zero_checker(function): def inner(a, b): if b == 0: return 'You cannot divide by zero' return function(a, b) return inner def divide(a, b): return a / b
In the code above, the function
zero_checker, also our decorator, accepts another function as an argument. In the next line of code, we're creating another function called
inner. The name isn't important but you'll sometimes see developers naming it "wrapper". Inside that function is where we apply our zero checker logic. Remember, once a function reaches its first
return statement, Python escapes the function. This means only one return statement can be executed per function invocation.
Now, this code doesn't do anything special because we have not connected the two functions. Consider this code:
def zero_checker(function): def inner(a, b): if b == 0: return 'You cannot divide by zero' return function(a, b) return inner def divide(a, b): return a / b divide = zero_checker(divide) print(divide(10, 0))
The second to last line of code is where we apply our decorator. We are setting the variable
zero_checker and passing it our
Let's take it one step at a time.
When Python reaches the last line of code
print(divide(10, 0)), it knows to apply our decorator. So it goes up to the top line and works its way down. It then creates
inner() and checks to see if
b is equal to zero. It is equal to zero so it returns
You cannot divide by zero.
Let's take a look at the same example except replace
print(divide(10, 0)) with
Python sees the print statement at the end and jumps up to the
zero_checker function because we declared
divide = zero_checker(divide). It creates
inner() and does the check. This time it is not true so it skips that block of code and returns our passed function
divide thus executing it and returning
Since Python developers have used lines similar to
divide = zero_checker(divide) we can remove it completely and just add this one line of code above our
@zero_checker def divide(a, b): return a / b
I know it can be difficult grasping this concept via a blog post and sometimes many YouTube videos touch on so many subjects before getting to decorators so I suggest you cut and paste the complete code below to this Python visualizing tool!
def zero_checker(function): def inner_function(a, b): if b == 0: return 'You cannot divide by zero' return function(a, b) return inner_function @zero_checker def divide(a, b): return a / b print(divide(10, 0))