Software, Tech & Coding simplified.

# What Are Decorators in Python Python decorators provide a readable way to extend the behavior of a function, method, or class.

Decorating a function in Python follows this syntax:

```@guard_zero
def divide(x, y):
return x / y```

Here the `guard_zero` decorator updates the behavior of `divide()` function to make sure `y` is not `0` when dividing.

### How to Use Decorators in Python

The best way to demonstrate using decorators is by an example.

Let’s first create a function that divides two numbers:

```def divide(x, y):
return x / y```

The issue with this function is it allows divisions by 0, which is illegal mathematically. You could solve this problem by adding an `if` check.

However, there is another option called decorators. Using a decorator, you do not change the implementation of the function. Instead, you extend it from outside. For now, the benefit of doing this is not apparent. We will come back to it later on.

Let’s start by creating the `guard_zero` decorator function that:

1. Takes a function as an argument.
2. Creates an extended version of it.
3. Returns the extended function.

Here is how it looks in code:

```def guard_zero(operate):
def inner(x, y):
if y == 0:
print("Cannot divide by 0.")
return
return operate(x, y)
return inner```

Here:

• The `operate` argument is a function to extend.
• The `inner` function is the extended version of the `operate` function. It checks if the second input argument is zero before it calls `operate`.
• Finally, the `inner` function is returned. It is the extended version of `operate`, the original funtion passed as an argument.

You can now update the behavior of your `divide` function by passing it into the `guard_zero`. This happens by reassigning the extended `divide` function to the original one:

`divide = guard_zero(divide)`

Now you have successfully decorated the divide function.

However, when talking about decorators, there is a more Pythonic way to use them. Instead of passing the extended object as an argument to the decorator function you can “mark” the function with the decorator using the `@` symbol:

```@guard_zero
def divide(x, y):
return x / y```

This is a more convenient way to apply decorators in Python. It also looks syntactically nice and the intent is clear.

Now you can test that the `divide` function was really extended with different inputs:

```print(divide(5, 0))

print(divide(5, 2))```

Output:

```Cannot divide by 0.
None

2.5```

(A `None` appears in the output because `guard_zero` returns `None` when `y` is `0`.)

Here is the full code used in this example for your convenience:

```def guard_zero(operate):
def inner(x, y):
if y == 0:
print("Cannot divide by 0.")
return
return operate(x, y)
return inner

@guard_zero
def divide(x, y):
return x / y

print(divide(5, 0)) # prints "Cannot divide by 0"```

Now you know how to use a decorator to extend a function. But when is this actually useful?

### When to Use Decorators in Python

Why all the hassle with a decorator? In the previous example, you could have created an if-check and saved 10 lines of code.

Yes, the decorator in the previous example was overkill. But the power of decorators becomes clear when you can avoid repetition and improve overall code quality.

Imagine you have a bunch of similar functions in your project:

```def checkUsername(name):
if type(name) is str:
print("Correct format.")
else:
print("Incorrect format.")

def checkName(name):
if type(name) is str:
print("Correct format.")
else:
print("Incorrect format.")
print("Handling name completed.")

def checkLastName(name):
if type(name) is str:
print("Correct format.")
else:
print("Incorrect format.")
print("Handling last name completed.")```

As you can see, these functions all have the same if-else statement for input validation. This introduces a lot of unnecessary repetition in the code.

Let’s improve this piece of code by implementing an input validator decorator. In this decorator, we perform the repetitive if-else checks altogether:

```def string_guard(operate):
def inner(name):
if type(name) is str:
print("Correct format.")
else:
print("Incorrect format.")
operate(name)
return inner```

This decorator:

• Takes a function as an argument.
• Extends the behavior to check if the input is a string.
• Returns the extended function.

Now, instead of repeating the same if-else in each function, you can decorate each function with the function that performs the if-else checks:

```@string_guard

@string_guard
def checkName(name):
print("Handling name completed.")

@string_guard
def checkLastName(name):
print("Handling last name completed.")```

This is much cleaner than the if-else mess. Now the code is more readable and concise. Better yet, if you need more similar functions in the future, you can apply the `string_guard` to those as well.

Now you know how decorators can help you write cleaner code and reduce unwanted repetition.

Next, let’s take a look at some common built-in decorators you need to know about in Python.

## @Property Decorator in Python

Decorating a method in a class with `@property` makes it possible to call a method like accessing an attribute:

`weight.pounds() ---> weight.pounds`

Let’s see how it works and when you should use it.

### Example

Let’s create a `Mass` class that stores mass in kilos and pounds:

```class Mass:
def __init__(self, kilos):
self.kilos = kilos
self.pounds = kilos * 2.205```

You can use this class as follows:

```mass = Mass(1000)

print(mass.kilos)
print(mass.pounds)```

Output:

```1000
2205```

Now, let’s modify the number of `kilos`, and see what happens to `pounds`:

```mass.kilos = 1200
print(mass.pounds)```

Output:

`2205`

Changing the number of `kilos` did not affect the number of `pounds`. This is because you did not update the `pounds`. Of course, this is not what you want. It would be better if the `pounds` property would be updated at the same time.

To fix this, you can replace the `pounds` attribute with a `pounds()` method. This method computes the `pounds` on-demand based on the number of `kilos`.

```class Mass:
def __init__(self, kilos):
self.kilos = kilos

def pounds(self):
return self.kilos * 2.205```

Now you can test it:

```mass = Mass(100)
print(mass.pounds())

mass.kilos = 500
print(mass.pounds())```

Result:

```220.5
1102.5```

This works like a charm.

However, now calling `mass.pounds` does not work as it is no longer a variable. Thus, if you call `mass.pounds` without parenthesis anywhere in the code, the program crashes. So even though the change fixed the problem, it introduced syntactical differences.

Now, you could go through the whole project and add the parenthesis for each `mass.pounds` call.

But there is an alternative.

Use the `@property` decorator to extend the `pounds()` method. This turns the method into a getter method. This means it is still accessible similar to a variable even though it is a method. In other words, you do not need to use parenthesis with this method call:

```class Mass:
def __init__(self, kilos):
self.kilos = kilos

@property
def pounds(self):
return self.kilos * 2.205```

For example:

```mass = Mass(100)
print(mass.pounds)

mass.kilos = 500
print(mass.pounds)```

Using the `@property` decorator thus reduces the risk of making the old code crash due to the changes in syntax.

## @Classmethod Decorator in Python

A class method is useful when you need a method that involves the class but is not instance-specific.

A common use case for class methods is a “second initializer”.

To create a class method in Python, decorate a method inside a class with `@classmethod`.

### Class Method as a Second Initializer in Python

Let’s say you have a `Weight` class:

```class Weight:
def __init__(self, kilos):
self.kilos = kilos```

You create `Weight` instances like this:

`w = Weight(100)`

But what if you wanted to create a weight from pounds instead of kilos? In this case, you need to convert the number of kilos to pounds beforehand:

```pounds = 220.5
kilos = pounds / 2.205

w2 = Weight(kilos)```

But this is bad practice and if done often, it introduces a lot of unnecessary repetition in the code.

What if you could create a `Weight` object directly from pounds with something like `weight.from_pounds(220.5)`?

To do this, you can write a second initializer for the `Weight` class. This is possible by using the `@classmethod` decorator:

```class Weight:
def __init__(self, kilos):
self.kilos = kilos

@classmethod
def from_pounds(cls, pounds):
kilos = pounds / 2.205
return cls(kilos)```

Let’s take a look at the code to understand how it works:

• The `@classmethod` turns the `from_pounds()` method into a class method. In this case, it becomes the “second initializer”.
• The first argument `cls` is a mandatory argument in a class method. It’s similar to `self`. The `cls` represents the whole class, not just an instance of it.
• The second argument `pounds` is the number of pounds you are initializing the `Weight` object form,
• Inside the `from_pounds` method, the `pounds` are converted to `kilos`.
• Then the last line returns a new `Weight` object generated from `pounds`. (`cls(kilos)` is equivalent to `Weight(kilos)`)

Now it is possible to create a `Weight` object directly from a number of pounds:

```w = Weight.from_pounds(220.5)
print(w.kilos)```

Output:

`100`

## @Staticmethod Decorator in Python

A static method in Python is a method tied to a class, not to an instance of it

A static method could also be a separate function outside the class. But as it closely relates to the class, it is placed inside of it.

A static method does not take reference argument `self` because it cannot access or modify the attributes of a class. It’s an independent method that works the same way for each object of the class.

To create a static method in Python, decorate a method in a class with the `@staticmethod` decorator.

For example, let’s add a static method `conversion_info` into the `Weight` class:

```class Weight:
def __init__(self, kilos):
self.kilos = kilos

@classmethod
def from_pounds(cls, pounds):
kilos = pounds / 2.205
return cls(kilos)

@staticmethod
def conversion_info():
print("Kilos are converted to pounds by multiplying by 2.205.")```

To call this method, you can call it on the `Weight` class directly instead of creating a `Weight` object to call it on.

`Weight.conversion_info()`

Output:

`Kilos are converted to pounds by multiplying by 2.205.`

Because the method is static, you can also it on a `Weight` object.

## Conclusion

In Python, you can use decorators to extend the functionality of a function, method.

For example, you can implement a `guard_zero` decorator to prevent dividing by 0. Then you can extend a function with it:

```@guard_zero
def divide(x, y):
return x / y```

Decorators are useful when you can avoid repetition and improve code quality.

There are useful built-in decorators in Python such as `@property`, `@classmethod`, and `@staticmethod`. These decorators help you make your classes more elegant. Under the hood, these decorators extend the methods by feeding them into a decorator function that updates the methods to do something useful.