Let's do some decorating!
Tags: python, sql
Published: 2024-01-06 00:43:33


Just like in JS, functions are **first-class methods**, meaning that they can be passed as an argument inside another function. And that's what Decorators do, they are essentially a function that you wrap around another function using the @ syntax.

In Python there are a number of built in functions that we see as decorators. Some like property can be used as a decorator or as a regular function.

Some common decorators include:

- @classmethod 
- @property 
- @wraps 
- @abstractmethod 
- @staticmethod 

So far, in my learning, I have really only used the 1st two examples above. Lets dive into them.

**@classmethod** 
A class method binds to the _class_ itself and not the instance, or object, of the _class_. Class methods take the _class_ as the first argument, as "cls", rather than instance(self).
Class methods can modify a class state that applies then to all the instances of the class.
An example of a class method that we used:


![create_table class method](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qpumjgxdkl454icj4ox6.png)

In the create_table class method, we are creating a table that maps our Enemy class to a database called enemies. This new table will persist the attributes of all Enemy instances.

**@property** 
Properties are attributes that are controlled by methods. We use a method to get the attribute and another method to set the attribute. The setter usually defines things like whether the attribute needs to be a string(str) or an integer(int), as well as if it's mutable or how long it can be.

The property method can be written as a regular function, like so:

![property method](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z54jwd4cqve87ogehwpe.png)

Or it can be used as a decorator, like this:

![property decorator](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oj2rme6fo0rw00qoncrl.png)

The property() was created before the @decorator syntax, but the @property decorator is most often used today.
----
Now, a little bit about the other examples that I listed, but didn't learn in school.

**@staticmethod** 
The static method, like the class method, belongs to the class itself, and not the instance of the class. But unlike @classmethod it does not have access to an instance or its attributes, and therefore cannot modify them. Hence, static - lacking in movement, action or change(Oxford Dictionary)!

![static method, math example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/glu7i8cnwdhcgkmz5fcj.png)

What situation would you use @staticmethod: 
- when you need a function that is related to the class, but doesn't depend on the state of instances.
- when you want to define a function that calls directly on the class.

**@abstractmethod** 
We did not learn about the abstract base class, but they are what use the abstract method. An example of the use for the abstract method would be on something like a Shape class that is the base class for other classes of shapes:

![Shape base abstract method](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0z6s8edp9vko8x8xvnju.png)

Above, Shape is an abstract base class with 2 defined  @abstractmethod's, are and perimeter. The Circle and the Square classes, or subclasses, must implement the abstract methods that the base class defined. If the subclasses don't implement the methods then the class becomes an abstract base class as well. And in so doing, the user won't be able to create instances, they will get a TypeError.

When should you use @abstractmethod? 
- when a user wants to ensure that all subclasses of a base class implement certain methods and behaviors.
- when a user wants to define a base class to provide common interfaces for a group of subclasses.

*Don't forget to import ABC and abstractmethod from abc. 😉

**@wraps**
The wraps decorator is a utility decorator that is part of Python's builtin functools module.
A function defined with the wraps decorator gets its original metadata, behavior, and attributes preserved.
Here are two examples of its use:

![wraps decorator log function](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qrxnje3tc1i4ros5vrp1.png)

```
==> Output: "Calling function add_numbers with args: (3, 5), kwargs: {}
```


![wraps decorator wrapper function](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kzszwx5ej65kc4ft8chr.png)

When to use @wraps? 
- when a user wants to define a decorator that wraps around  another function, and you want the wrapped function to preserve the metadata of the original function.
   - in other words: To use the wraps decorator is to create custom decorators. These custom decorators wrap other functions with the original function.

*remember to import wraps from functools! 😉


------
Sources:
"classmethod() in python". @GeeksforGeeks, Sanchhaya Education Limited. Retrieved 4 Jan. 2024 from https://www.geeksforgeeks.org/classmethod-in-python/

Dr. Karhade, M. MD. PhD. "Most use python decorators: when to use and when not to". Published 22 March 2023. Retrieved on 5 Jan. 2024 from https://levelup.gitconnected.com/most-used-python-decorators-when-to-use-and-when-not-to-32b8f883ee43

Mwiti, D. "Python decorators tutorial". Published Oct. 2017. Retrieved on 4 Jan. 2023 from  https://www.datacamp.com/tutorial/decorators-python