What Are Python Decorators Anyway?
Tags: python, beginners
Published: 2020-04-05 17:54:09

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:
```py
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:

```py
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:

```py
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:

```py
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 `divide` to `zero_checker` and passing it our `divide` function.

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 `print(divide(10, 2))`

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 `5`.

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 `divide` function:

```py
@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](http://www.pythontutor.com/visualize.html#mode=edit)!

```py
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))

```