Decorators are a powerful and flexible feature in python way to modify or enhance functions or methods without changing their existing code, they allow you to wrap another function, adding functionality before or after the wrapped function runs.

Define Decorator

A decorator is a function that takes another function as argument and returns a new function. This new function can add behaviour to the original function.

Syntax for Decorators

Typically, decorators use the @decorator_name placed above or below of the function definition as shown in the below example.

Example 1: Here is the basic example which illustrates working of Decorators.

This decorator will print a message before and after the execution of a function.

    def log_decorator(func):
    def wrapper (*args, **kwargs):         
      print (f"Calling function: {func.__name__}")         
      result = func(*args, **kwargs)  # Call the original function         
      print (f"Function {func.__name__} finished")         
      return result     
    return wrapper  

@log_decorator 
def greet(name):     
    return f"Hello, {name}!"  
# Usage 
print(greet("Madhu"))

Here what the decorator does

1. log_decorator is defined to take a function func as an argument.Inside, it defines a wrapper function that adds logging behavior.

2. Logging: The wrapper function prints a message before calling the original function and another message after it finishes.

3. Applying the Decorator: The @log_decorator syntax applies the decorator to the greet function. When you call greet("Madhu"), it actually calls wrapper.

Output

When you run the code, you’ll see:
Calling function: greet
function: greet finished
Hello, Madhu!

Simplicity : This example illustrates how decorators can add functionality (logging) in a clear and simple way.
Functionality Separation: The logging is separated from the core logic of the greet function, making the code cleaner and easier to maintain.
This example shows the basic mechanism of decorators in a straightforward manner!

Example 2: Authorization Decorator

This decorator will simulate user authorization by checking if a user has the right permissions to execute a function.

def requires_authentication(func):     
    def wrapper(user, *args, **kwargs):         
        if not user.get('is_authenticated', False):             
            raise PermissionError("User is not authorized to perform this action.")         
        return func(user, *args, **kwargs)  # Call the original function     
    return wrapper  
        
@requires_authentication 
def access_secure_resource(user):     
    return "Access granted to secure resource!"  
    
# Usage example 
user1 = {'name': 'Alice', 'is_authenticated': True} 
user2 = {'name': 'Bob', 'is_authenticated': False}  

# Authorized user 
try:     
   print(access_secure_resource(user1))  # Output: Access granted to secure resource! 
except PermissionError as e:     
   print(e)  
# Unauthorized user 
try:     
    print(access_secure_resource(user2))  # Raises PermissionError 
except PermissionError as e:     
    print(e) # Output: User is not authorized to perform this action. 
Explanation

1. Decorator Definition:

The requires_authentication decorator takes a function func as an argument. Inside, it defines a wrapper function that checks the user object for an is_authenticated attribute.

2. Authorization Check:

If the user is not authenticated, a PermissionError is raised. If the user is authenticated, the original function func is called with the user and any additional arguments.

3. Applying the Decorator:

The @requires_authentication syntax applies the decorator to access_secure_resource. When access_secure_resource(user) is called, it first goes through the wrapper function.

Flexibility: You can customize the authorization logic based on your application needs.
Error Handling: The decorator can raise exceptions, which can be caught and handled in the calling code.
Reusable: This decorator can be reused with any function that requires user authentication.

This example showcases how decorators can encapsulate cross-cutting concerns like authentication, keeping your code clean and focused on its core functionality.