30. Decorators - MantsSk/CA_PTUA14 GitHub Wiki

Decorators accept functions, add additional functionality to those functions, and return a result. Decorators are a bit challenging to understand, and perhaps in practice, you may never need to write your own decorator. However, it's important to understand their principles because you will encounter them frequently in various Python frameworks.

Functions:

def add_one(number):
    return number + 1

print(add_one(2))

Functions passed as parameters:

def greet(name):
    return f"Hello {name}"

def dance(name):
    return f"{name}, let's dance"

def welcome(func):
    return func("John")

print(welcome(greet))
print(welcome(dance))

Functions inside functions:

def parent():
    print("Parent function")

    def child_1():
        print("Child 1 function")

    def child_2():
        print("Child 2 function")

    child_2()
    child_1()

Returning a function as the result of a function:

def parent(number):
    def child_1():
        return "First child"

    def child_2():
        return "Second child"

    if number == 1:
        return child_1
    else:
        return child_2

print(parent(1)())
print(parent(2)())

Simple decorator:

def my_decorator(func):
    def wrapper():
        print("Start")
        func()
        print("End")
    return wrapper

def greet():
    print("hello")

greet = my_decorator(greet)

greet()

Using the @ syntax:

Copy code
def my_decorator(func):
    def wrapper():
        print("Start")
        func()
        print("End")
    return wrapper

@my_decorator
def greet():
    print("hello")

greet()

A decorator is essentially a simple function, so it can be created in another file, imported wherever needed. The functionality can also be customized as desired.

Create a file decorators.py and put this code in it:

Copy code
def do_three_times(func):
    def wrapper_do_three_times():
        func()
        func()
        func()
    return wrapper_do_three_times

Then import and use it in the main file:

from decorators import do_three_times

@do_three_times
def greet():
    print("Hello!")

greet()

If you create a greeting function that takes a parameter, you will get an error because the decorator's wrapper function does not have parameters:

from decorators import do_three_times

@do_three_times
def greet():
    print("Hello!")

@do_three_times
def greet_with_name(name):
    print(f"Hello, {name}")

greet()
greet_with_name("Peter")

To fix this, you can use *args and **kwargs in the decorator's wrapper function to indicate that the function may have one or more parameters:

def do_three_times(func):
    def wrapper_do_three_times(*args, **kwargs):
        func(*args, **kwargs)
        func(*args, **kwargs)
        func(*args, **kwargs)
    return wrapper_do_three_times

If a function needs to return something, the current decorator won't return anything:

@do_three_times
def greet_with_return(name):
    print(f"Hello, {name}!")
    return name

print(greet_with_return("Jonas"))

We need to specify the return in the decorator:

def do_three_times(func):
    def wrapper_do_three_times(*args, **kwargs):
        func(*args, **kwargs)
        func(*args, **kwargs)
        return func(*args, **kwargs)
    return wrapper_do_three_times

In practice, writing your own decorator is unlikely, but they are widely used in backend frameworks such as Django or Flask.

Tasks

Task 1

Create a Python program that defines a simple function with a decorator. Implement the following:

Write a function called greet(name) that returns a greeting message. Implement a decorator uppercase_decorator that converts the greeting message to uppercase.

Task 2

  1. Create 4 functions that do basic operations (addition, subtraction, multiplication, division).
  2. Implement a decorator that prints the details of each operation to terminal: function name and result in this format, for example: Operation: add, Result: 8
  3. Use the decorator with all 4 functions
  4. Call each function at least once

Task 3

Create a Python program to handle a basic to-do list. Implement the following:

  1. Initialize an empty list called todo_list to store tasks.
  2. Write a function add_task(task) that adds a task to the to-do list.
  3. Write a function complete_task(task) that marks a task as completed and removes it from the to-do list.
  4. Implement a decorator print_todo that prints the current to-do list before and after executing the decorated function.

Task 4

  1. Create a decorator, that prints execution time of the function in format: {function_name} took {time} to execute.
  2. Decorator must work with any function regardless of amount of parameters that function has or whether a function returns value or not.
  3. Test the decorator with multiple different functions

Task 5

  1. Create a decorator, that changes the returned value of the function - whatever text function returns, decorator should style it like this:

Function returns:

Styled

Decorator should change the output and return this instead:

+------------------+
| S  t  y  l  e  d |
+------------------+
  1. Test the decorator with various functions that return any kind of text