Python Intermediate and Advanced

Python Intermediate_006: Decorators in Python

codeaddict 2025. 3. 4. 22:34

Decorators in Python are a powerful feature that allows you to modify or extend the behavior of functions without changing their actual code. In this blog post, we’ll explore two examples: a basic decorator to add behavior around a function and a more practical one to simulate access control. Let’s get started!


1. Basic Decorator Example

Here’s a simple decorator that adds some behavior before and after a function call:

 

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("1. Before function call")  
        result = func(*args, **kwargs)  
        print("2. After function call") 
        return result  
    return wrapper  

@my_decorator
def add(x, y):
    print("   → Inside function: Adding numbers")  # Step 3.1
    return x + y  

print("START")  
result = add(3, 4)  
print(f"RESULT: {result}")  
print("END")  

Output:

START
1. Before function call
   → Inside function: Adding numbers
2. After function call
RESULT: 7
END

Order of Execution

Let’s break down the order in which the code is executed:

 


2. Practical Example: Access Control Decorator

Now, let’s create a more useful decorator that simulates restricting access to a function based on a condition (e.g., user authentication). This is a simplified version of what you might see in web frameworks like Flask.

def require_auth(func):
    def wrapper(user, *args, **kwargs):
        # Simulate checking if the user is authenticated
        if user.get("is_authenticated", False):  # Check if user is authenticated
            print("1. Access granted!")
            return func(user, *args, **kwargs)   # Call the original function
        else:
            print("1. Access denied: Please log in.")
            return None                          # Return None if access is denied
    return wrapper

@require_auth
def view_profile(user):
    print("   → Inside function: Fetching profile data")
    return f"Profile: {user['username']}"

# Test with an unauthenticated user
user1 = {"username": "Alice", "is_authenticated": False}
print("START")
result = view_profile(user1)
print(f"RESULT: {result}")
print("END")

# Test with an authenticated user
user2 = {"username": "Bob", "is_authenticated": True}
print("\nSTART")
result = view_profile(user2)
print(f"RESULT: {result}")
print("END")

Output:

START
1. Access denied: Please log in.
RESULT: None
END

START
1. Access granted!
   → Inside function: Fetching profile data
RESULT: Profile: Bob
END

 

Order of Execution (for user2)

 

  • Step 1: @require_auth — The view_profile function is wrapped by require_auth.
  •  Step 2: print(“START”) — Prints “START” before calling the function.
  •  Step 3: result = view_profile(user2) → calls wrapper — The decorator runs before view_profile().
  •  Step 4: if user.get(“is_authenticated”, False) — Checks if the user is authenticated (True for user2).
  •  Step 5: print(“1. Access granted!”) — Prints “Access granted!” since the condition is met.
  •  Step 6: func(user, *args, **kwargs) → Calls view_profile — The original function view_profile is called with user2.
  •  Step 7: print(“ → Inside function: Fetching profile data”) — Prints inside view_profile().
  •  Step 8: return f”Profile: {user[‘username’]}” — Returns “Profile: Bob” to wrapper.
  •  Step 9: return func(…) — wrapper returns the result to result.
  •  Step 10: print(f”RESULT: {result}”) — Prints “RESULT: Profile: Bob”.
  •  Step 11: print(“END”) — Prints “END”.

For user1: The decorator stops at step 4, prints “Access denied: Please log in.”, and returns None.

 

Decorators are incredibly versatile! The access control example shows how they’re used in frameworks to manage permissions. Try tweaking the require_auth decorator to log failed attempts or add more conditions! 😊