def simple_decorator(func): def wrapper(): print("Something is happening before the function is called.") func() print("Something is happening after the function is called.") return wrapper @simple_decorator def say_hello(): print("Hello!") say_hello() # Now, the enhanced decorator def decorator_with_args(arg1, arg2): def decorator(func): def wrapper(*args, **kwargs): print(f"Decorator args: {arg1}, {arg2}") return func(*args, **kwargs) return wrapper return decorator @decorator_with_args("hello", 42) def print_numbers(a, b): print(a + b) print_numbers(10, 5) ''' How it works: 1. The `simple_decorator` function is defined, which takes a function `func` as an argument. 2. Inside `simple_decorator`, a `wrapper` function is defined that prints messages before and after calling `func`. 3. The `wrapper` function is returned from `simple_decorator`. 4. The `decorator_with_args` function is defined, which takes two arguments `arg1` and `arg2`. 5. Inside `decorator_with_args`, a `decorator` function is defined that takes a function `func` as an argument. 6. Inside `decorator`, a `wrapper` function is defined that prints the decorator arguments and then calls `func`. 7. The `wrapper` function is returned from `decorator`, and `decorator` is returned from `decorator_with_args`. 8. The `print_numbers` function is decorated with `@decorator_with_args("hello", 42")`. 9. When `print_numbers` is called, it prints the sum of its arguments and the decorator arguments. '''