# 22. Custom Decorators with Arguments

Custom decorators with arguments allow you to create more flexible and dynamic decorators by passing arguments to the decorator itself.

***

**1. Decorator with a Simple Argument**

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

@repeat(3)
def say_hello():
    print("Hello!")

say_hello()
```

**Output:**

```
Hello!
Hello!
Hello!
```

***

**2. Logging Decorator with a Custom Message**

```python
def log_message(message):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f"[LOG] {message}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@log_message("Executing add function")
def add(a, b):
    return a + b

print(add(3, 5))
```

**Output:**

```
[LOG] Executing add function
8
```

***

**3. Timing Decorator with a Label**

```python
import time

def timer(label):
    def decorator(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            print(f"[{label}] Execution time: {end_time - start_time:.2f}s")
            return result
        return wrapper
    return decorator

@timer("Slow Function")
def slow_function():
    time.sleep(2)

slow_function()
```

**Output:**

```
[Slow Function] Execution time: 2.00s
```

***

**4. Access Control Decorator with a Role**

```python
def require_role(role):
    def decorator(func):
        def wrapper(user_role, *args, **kwargs):
            if user_role == role:
                return func(*args, **kwargs)
            else:
                print(f"Access denied: requires role '{role}'")
        return wrapper
    return decorator

@require_role("admin")
def delete_user(user_id):
    print(f"User {user_id} deleted!")

delete_user("admin", 42)  # Success
delete_user("guest", 42)  # Access denied
```

***

**5. Rate Limiter with a Custom Limit**

```python
def rate_limiter(limit):
    call_count = 0

    def decorator(func):
        def wrapper(*args, **kwargs):
            nonlocal call_count
            if call_count < limit:
                call_count += 1
                return func(*args, **kwargs)
            else:
                print("Rate limit exceeded!")
        return wrapper
    return decorator

@rate_limiter(3)
def greet():
    print("Hello!")

greet()
greet()
greet()
greet()  # Exceeds limit
```

***

**6. Dynamic Prefix Decorator**

```python
def add_prefix(prefix):
    def decorator(func):
        def wrapper(*args, **kwargs):
            return f"{prefix} {func(*args, **kwargs)}"
        return wrapper
    return decorator

@add_prefix("Greeting:")
def greet(name):
    return f"Hello, {name}"

print(greet("Alice"))
```

**Output:**

```
Greeting: Hello, Alice
```

***

**7. Validation Decorator with Custom Error Message**

```python
def validate_positive(error_message):
    def decorator(func):
        def wrapper(value):
            if value < 0:
                raise ValueError(error_message)
            return func(value)
        return wrapper
    return decorator

@validate_positive("Value must be positive!")
def square_root(value):
    return value ** 0.5

print(square_root(4))  # Success
print(square_root(-1))  # Raises ValueError
```

***

**8. Custom Logger Decorator with Log Levels**

```python
def logger(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f"[{level}] Calling {func.__name__}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@logger("DEBUG")
def multiply(a, b):
    return a * b

print(multiply(3, 4))
```

**Output:**

```
[DEBUG] Calling multiply
12
```

***

**9. Retry Decorator with Retry Count**

```python
def retry(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < n:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"Attempt {attempts + 1} failed: {e}")
                    attempts += 1
            print("All attempts failed.")
        return wrapper
    return decorator

@retry(3)
def risky_operation():
    import random
    if random.choice([True, False]):
        print("Success!")
    else:
        raise ValueError("Random failure!")

risky_operation()
```

***

**10. Environment-Specific Execution Decorator**

```python
def run_in_env(env):
    def decorator(func):
        def wrapper(*args, **kwargs):
            current_env = "production"  # Simulated environment
            if current_env == env:
                return func(*args, **kwargs)
            else:
                print(f"Function can only run in the '{env}' environment.")
        return wrapper
    return decorator

@run_in_env("development")
def debug_mode():
    print("Debugging application...")

debug_mode()  # Will not execute unless the environment is "development"
```

***
