# Python decorators simplified

Put simply: Decorators wrap a function, modifying its behavior.

A simple decorator example:

```python
def my_decorator(function):
    def wrapper():
        print("Something is happening before the function is called.")
        function()
        # do other stuff
        print("Something is happening after the function is called.")
    return wrapper
```

In this example, this decorator is changing the function's behavior by doing extra stuff before and after the function call.

There are mainly two ways we can use the above decorator.

**Method 1:**

```python
def say_whee():
    print("Whee!")

my_decorator(say_whee)
```

**Method 2:**

```python
@my_decorator
def say_whee():
    print("Whee!")
```

We will use the 2nd method in upcoming examples as it’s more readable.

Example use:

1. we can use a decorator to add logs before and after the execution of functions.
    
2. we can use a decorator to calculate and print the execution time of functions.
    

**Decorator for function with return value:**

```python
def my_decorator(function):
    def wrapper():
        print("Something is happening before the function is called.")
        value = function()
        # do other stuff
        print("Something is happening after the function is called.", value)
        return value
    return wrapper
```

Notice that we have just added the return value we got from calling the function.

Example use:

```python
@my_decorator
def say_whee():
    print("Whee!")
    return 100
```

**Decorator for function with arguments:**

```python
def my_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper
```

```python
@my_decorator
def say_whee(name):
    print("Hello, " + name)
    return 100
```

  
**Passing arguments to the decorator:**

```python
def exception_handler(except_returns=None):  # for multiple values pass in tuple e.g (value1, value2)
    def decorator(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            try:
                return function(*args, **kwargs)
            except Exception as e:
                exc_type, exc_value, exc_traceback = sys.exc_info()
                message = f"Exception in {function.__name__}, {exc_value}"
                print(message)
                return except_returns
        return wrapper
    return decorator
```

Notice that we are accepting values in the except\_returns argument. The idea is to use this value when an exception occurs.

**Technical Detail:** The `@functools.wraps` decorator uses the function `functools.update_wrapper()`  
to update special attributes like \_\_name\_\_ and \_\_doc\_\_ that are used in the introspection.

  
**Use:**

```python
@exception_handler(0)
def function_with_exception(value, val2, val3):
    print(a) # a is not defined
    return 100

value = function_with_exception(1, 2, 3)  # this will return 0
```

This article is a simplified version of these sources:

For ref: [https://realpython.com/primer-on-python-decorators/#decorators-with-arguments](https://realpython.com/primer-on-python-decorators/#decorators-with-arguments)

Class decorators: [https://stackoverflow.com/a/9647491](https://stackoverflow.com/a/9647491)

decorators with args: [https://stackoverflow.com/questions/5929107/decorators-with-parameters](https://stackoverflow.com/questions/5929107/decorators-with-parameters)
