# 125. Decorators with Arguments

Here are 10 Python code snippets demonstrating how to create and use decorators that accept arguments for customization:

**1. Basic Decorator with Arguments**

```python
def greet(message):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(message)
            return func(*args, **kwargs)
        return wrapper
    return decorator

@greet("Hello, World!")
def say_name(name):
    print(f"My name is {name}")

say_name("John")  # Output: Hello, World! My name is John
```

***

**2. Decorator with Arguments for Logging**

```python
def log_function_calls(log_message):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f"{log_message} - Calling function: {func.__name__}")
            result = func(*args, **kwargs)
            print(f"{log_message} - Function {func.__name__} completed")
            return result
        return wrapper
    return decorator

@log_function_calls("LOG")
def add(x, y):
    return x + y

add(2, 3)
```

***

**3. Decorator with Arguments for Timing**

```python
import time

def time_it(unit="seconds"):
    def decorator(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            elapsed_time = end_time - start_time
            if unit == "seconds":
                print(f"Time taken: {elapsed_time} seconds")
            elif unit == "milliseconds":
                print(f"Time taken: {elapsed_time * 1000} milliseconds")
            return result
        return wrapper
    return decorator

@time_it(unit="milliseconds")
def slow_function():
    time.sleep(1)

slow_function()
```

***

**4. Decorator with Arguments for Authorization**

```python
def requires_permission(permission):
    def decorator(func):
        def wrapper(*args, **kwargs):
            user_permission = 'admin'  # Simulate user permission
            if user_permission != permission:
                print("Unauthorized access")
                return
            return func(*args, **kwargs)
        return wrapper
    return decorator

@requires_permission('admin')
def access_admin_panel():
    print("Accessing admin panel")

access_admin_panel()  # Output: Accessing admin panel

@requires_permission('user')
def access_admin_panel():
    print("Accessing admin panel")

access_admin_panel()  # Output: Unauthorized access
```

***

**5. Decorator for Repeating Function Execution**

```python
def repeat_execution(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat_execution(times=3)
def say_hello():
    print("Hello!")

say_hello()  # Output: Hello! Hello! Hello!
```

***

**6. Decorator with Arguments for Caching**

```python
def cache_result(func):
    cache = {}
    def wrapper(*args):
        if args in cache:
            print("Fetching from cache")
            return cache[args]
        result = func(*args)
        cache[args] = result
        return result
    return wrapper

@cache_result
def compute_sum(a, b):
    print("Calculating sum")
    return a + b

compute_sum(1, 2)  # Output: Calculating sum, 3
compute_sum(1, 2)  # Output: Fetching from cache, 3
```

***

**7. Decorator with Arguments for Retry Logic**

```python
import time

def retry(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"Error: {e}. Retrying...")
                    time.sleep(1)
        return wrapper
    return decorator

@retry(times=3)
def may_fail():
    print("Attempting...")
    raise ValueError("Something went wrong")

may_fail()
```

***

**8. Decorator with Arguments for Method Timing**

```python
import time

def timed(method_name):
    def decorator(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            print(f"{method_name} took {end_time - start_time:.4f} seconds")
            return result
        return wrapper
    return decorator

@timed("Sorting")
def sort_list(lst):
    lst.sort()

sort_list([5, 3, 8, 6])  # Output: Sorting took 0.0000 seconds
```

***

**9. Decorator with Arguments for Changing Function Behavior**

```python
def with_custom_message(message):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(message)
            return func(*args, **kwargs)
        return wrapper
    return decorator

@with_custom_message("Executing custom behavior before function call.")
def display_number(number):
    print(f"Number: {number}")

display_number(42)
```

***

**10. Decorator with Arguments for Conditional Execution**

```python
def execute_if_true(condition):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if condition:
                return func(*args, **kwargs)
            else:
                print("Condition not met, function not executed.")
        return wrapper
    return decorator

@execute_if_true(condition=True)
def perform_task():
    print("Task performed.")

perform_task()  # Output: Task performed.

@execute_if_true(condition=False)
def perform_task():
    print("Task performed.")

perform_task()  # Output: Condition not met, function not executed.
```

***

These examples demonstrate how to create and use decorators that accept arguments, allowing for flexible customization of behavior such as logging, timing, caching, retries, and more. Decorators are a powerful tool in Python for enhancing and modifying function behavior dynamically.
