In Python, a decorator is a special type of function that can be used to modify or enhance the behavior of another function. Decorators are used to wrap an existing function and add some additional functionality to it without modifying the original code.
The syntax for using a decorator is to prefix the original function with the decorator function name, preceded by the “@” symbol. Here’s an example of a simple decorator:
def my_decorator(func): def wrapper(): print("Before the function is called.") func() print("After the function is called.") return wrapper @my_decorator def say_hello(): print("Hello!") say_hello()
In this example, the my_decorator
function is defined, which takes a function as an argument and returns a new function called wrapper
. The wrapper
function adds some extra functionality to the original function and then calls it.
To use the my_decorator
function to modify the say_hello
function, the @my_decorator
syntax is used before the function definition. This tells Python to pass the say_hello
function to the my_decorator
function as an argument.
When the say_hello
function is called, it is actually calling the wrapper
function returned by the my_decorator
function. This means that the code inside the wrapper
function will execute before and after the code inside the say_hello
function.
In this example, the output will be:
Before the function is called. Hello! After the function is called.
This shows that the my_decorator
function has successfully added some additional functionality to the say_hello
function without modifying the original code.
What are the functions in Python?:
In Python, a function is a block of code that performs a specific task or set of tasks. Functions are used to break down a program into smaller, more manageable pieces, making it easier to read, debug, and maintain.
To define a function in Python, the def
keyword is used, followed by the name of the function and a set of parentheses containing any arguments the function takes, and a colon. The function code block is indented below the header line, and any return statements are used to specify the value to be returned by the function. Here’s a simple example of a function that takes two arguments and returns their sum:
def add_numbers(x, y): result = x + y return result
In this example, the add_numbers
function takes two arguments, x
and y
, and adds them together, storing the result in a variable called result
. The function then uses a return statement to send the result back to the calling code.
To call the add_numbers
function, simply pass it two values as arguments, like this:
result = add_numbers(3, 5) print(result) # Output: 8
In this example, the add_numbers
function is called with the values 3 and 5 as arguments. The return value of the function, which is 8 in this case, is stored in a variable called result
, which is then printed to the console.
Python functions can also have optional parameters with default values, making it possible to call them with fewer arguments than defined. For example:
def greet(name, greeting="Hello"): print(greeting + ", " + name) greet("John") # Output: Hello, John greet("Mary", "Hi") # Output: Hi, Mary
In this example, the greet
function takes two parameters, name
and greeting
, where greeting
has a default value of “Hello”. If no value is provided for greeting
, the function will use the default value.
Decorating functions with parameters:
In Python, you can also define decorators that take arguments, allowing you to customize the behavior of the decorator based on the values of those arguments.
To define a decorator that takes arguments, you need to define a function that returns the actual decorator function. This decorator function will take the original function as an argument, along with any additional arguments passed to the decorator. Here’s an example:
def repeat(num_times): def decorator_repeat(func): def wrapper(*args, **kwargs): for i in range(num_times): result = func(*args, **kwargs) return result return wrapper return decorator_repeat @repeat(num_times=3) def greet(name): print(f"Hello, {name}!") greet("John")
In this example, the repeat
function is defined with a parameter called num_times
, which will be used to specify how many times the decorated function should be executed. The repeat
function returns a decorator function called decorator_repeat
.
The decorator_repeat
function takes the original function, func
, as an argument, and returns a new function called wrapper
. This function will execute the original function num_times
number of times, using a for loop. The *args
and **kwargs
syntax are used to allow any number of positional and keyword arguments to be passed to the original function.
To use this decorator with the greet
function, the @repeat(num_times=3)
syntax is used before the function definition. This tells Python to pass the value of num_times
as an argument to the repeat
function, and then pass the result of that function as an argument to the greet
function.
When the greet
function is called with the argument "John"
, it will be executed three times because of the repeat
decorator. The output will be:
Hello, John! Hello, John! Hello, John!
This example shows how you can use a decorator with parameters to modify the behavior of a function in a customizable way.
Fancy Decorators:
Fancy decorators are advanced techniques in Python that allow you to modify the behavior of a function in a more sophisticated way. They often involve using nested functions, closures, and other advanced concepts in Python.
Here are a few examples of fancy decorators in Python:
- Memoization decorator: This decorator stores the results of a function call in a cache, so that if the function is called again with the same arguments, it can return the cached result instead of recomputing it. Here’s an example:
def memoize(func): cache = {} def wrapper(*args): if args in cache: return cache[args] else: result = func(*args) cache[args] = result return result return wrapper @memoize def fibonacci(n): if n in (0, 1): return n return fibonacci(n-1) + fibonacci(n-2)
In this example, the memoize
decorator takes the original function, func
, as an argument, and returns a new function called wrapper
. The wrapper
function checks if the arguments passed to the original function are already in the cache. If so, it returns the cached result. If not, it calls the original function and stores the result in the cache before returning it.
The fibonacci
function is decorated with memoize
, so that the results of the function calls are stored in a cache. This significantly speeds up the computation of large Fibonacci numbers, which would otherwise require many recursive function calls.
- Timing decorator: This decorator measures the execution time of a function and prints the result to the console. Here’s an example:
import time def timing(func): def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) end = time.perf_counter() print(f"{func.__name__} took {end - start:.6f} seconds to execute") return result return wrapper @timing def factorial(n): if n == 0: return 1 else: return n * factorial(n-1)
In this example, the timing
decorator takes the original function, func
, as an argument, and returns a new function called wrapper
. The wrapper
function measures the execution time of the original function by recording the start and end times using the perf_counter()
function from the time
module. It then prints the execution time to the console before returning the result of the original function.
The factorial
function is decorated with timing
, so that the execution time of the function is printed to the console when it is called.
These are just a few examples of fancy decorators in Python. Decorators are a powerful feature of the language, and can be used to implement a wide variety of functionality.
Stateful Decorators:
Stateful decorators are decorators that maintain state across multiple calls to the decorated function. This can be useful when you want to keep track of some information between calls to the function, or modify the behavior of the function based on previous calls.
Here’s an example of a stateful decorator:
def counter(func): def wrapper(*args, **kwargs): wrapper.count += 1 return func(*args, **kwargs) wrapper.count = 0 return wrapper @counter def say_hello(name): print(f"Hello, {name}!") say_hello("John") say_hello("Jane") say_hello("Bob") print(f"say_hello was called {say_hello.count} times")
In this example, the counter
decorator takes the original function, func
, as an argument, and returns a new function called wrapper
. The wrapper
function counts the number of times it is called by incrementing its own count
attribute, and then calls the original function.
The wrapper
function also initializes its count
attribute to zero when it is defined. This attribute is then available to the decorated function as a property of the wrapper function.
The say_hello
function is decorated with counter
, so that each time it is called, the wrapper
function is executed and its count
attribute is incremented. Finally, the total number of times the function was called is printed to the console.
This is just one example of a stateful decorator. Other stateful decorators might maintain a cache of previously computed results, or modify the behavior of the function based on some internal state.
Classes as Decorators:
In Python, classes can be used as decorators. A decorator is a function that takes another function as an argument, modifies it, and returns the modified function. Decorators are a powerful feature of Python and are commonly used in web development frameworks like Flask and Django.
To use a class as a decorator, the class must have a __call__
method. The __call__
method is called when an instance of the class is called as a function. Here’s an example of a class-based decorator:
class MyDecorator: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print("Before the function is called.") self.func(*args, **kwargs) print("After the function is called.") @MyDecorator def my_function(): print("This is my function.") my_function()
In the example above, the MyDecorator
class takes a function as an argument in its constructor (__init__
). The __call__
method is called when the decorated function (my_function
) is called. The __call__
method prints a message before and after calling the decorated function.
When the my_function
function is called, it is wrapped by the MyDecorator
class, and the output of the program is:
Before the function is called. This is my function. After the function is called.
As you can see, the MyDecorator
class has modified the behavior of the my_function
function.