Day 5: Advanced Python: Decorators
Tags: python, beginners, programming, tutorial
Published: 2023-02-14 16:00:00


## What I learnt

- Decorators

  Decorators are a way to modify or extend the functionality of a class,method or function without changing the source code. Some implementations where decorators are commonly used are logging, caching, authentication and authorization.
  Decorators are usually written as functions that takes another function as argument and return a modified version of the original function.

### Example of decorators

```python
def decorator_func(func):
    def wrapper_func():
        print(f'This executes before {func.__name__}')
         return func()
    return wrapper_func

@decorator_func
def greeting():
    print('You\'re a great learner!')

greeting()

# Another common way decorators are being written is shown below
def greeting():
    print("You're doing well")

greet = decorator_func(greeting)
greet()
```

Decorating our functions allows us to easily add extra functionalities to existing function by adding that functionality inside of our wrapper.

## Decorators with Arguments

```python
def decorator_func(func):
    def wrapper_func(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper_func

@decorator_func
def user():
    print('User function called')

user()

@decorator_func
def user_profile(username, email, age):
    print('This is your profile info: \nUsername:{} \nEmail:{} \nAge:{}'.format(username, email, age))

user_profile('Jobizil', 'jobizil@email.com', '22')
```

### Using classes as decorators

```python
class decorator_class(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, \*\*kwargs):
        print(f'This executes before {self.func.__name__}')
    return self.func(*args, **kwargs)

@decorator_class
def user():
    print('User function called')
user()

@decorator_class
def user_profile(username, email, age):
    print('This is your profile info: \nUsername:{} \nEmail:{} \nAge:{}'.format(username, email, age))

user_profile('Jobizil', 'jobizil@email.com', '22')

```

### Other Practical examples with Decorators

- 1. Using decorators for logging

```python
from functools import wraps
import time

def logger(func):
    import logging
    logging.basicConfig(filename='{}.log'.format(
    func.**name**), level=logging.INFO)

    @wraps(func)
    def wrapper(*args, **kwargs):
        logging.info(f'Ran with args:{args}, and kwargs:{kwargs}')
        return func(*args, **kwargs)
    return wrapper

@logger
def user_profile(username, email, age):
    time.sleep(2)
    print('This is your profile info: \nUsername:{} \nEmail:{} \nAge:{}'.format( username, email, age))

user_profile('Jobizil', 'jobizil@email.com', '22')

```

- 2. Using decorators as timers to determine how long a function took to complete execution

```python
def timer(func):
    from time import time

    @wraps(func)
    def wrapper(*args, **kwargs):
        t1 = time()
        result = func(*args, **kwargs)
        t2 = time() - t1
        print(f'{func.__name__} took {t2}s to complete its execution.')
        return result
    return wrapper

@timer

def user_profile(username, email, age):
    time.sleep(2)
    print('This is your profile info: \nUsername:{} \nEmail:{} \nAge:{}'.format(username, email, age))
```

> To stack multiple decorators, we need to import the wraps decorator from the functools module as this helps preserve the original names of the functions.

```python
@timer
@logger
def user_profile(username, email, age):
    time.sleep(2)
    print('This is your profile info: \nUsername:{} \nEmail:{} \nAge:{}'.format(username, email, age))

user_profile('Jobizil', 'jobizil@email.com', '22')

```

### Exercises

```python
#  Create an @authenticated decorator that only allows the function to run is user1 has 'valid' set to True:
user = {
    'name': 'Sorna',
    'valid': False
}


def authenticated(fn):
    # Your code here

@authenticated
def message_friends(user):
    print('message has been sent')


message_friends(user)
```

## Resources

- [Python Decorators](https://realpython.com/primer-on-python-decorators/)
- [Corey Schafer: Python Decorators](https://www.youtube.com/watch?v=FsAPt_9Bf3U)

## Conclusion

I learnt about decorators and how they can be used to modify the functionality of a function without changing the source code. I also learnt how to write decorators and how to use them to add extra functionalities to existing functions.