- Decorating Methods defined in a Class With Python
- Python Decorators – How to Create and Use Decorators in Python With Examples
- When to Use a Decorator in Python
- Here are the building blocks used to create Python decorators
- How to Create a Python Decorator
- How to Add Arguments to Decorators in Python
- Example of a Python Decorator in Action
- Conclusion
Decorating Methods defined in a Class With Python
Decorators provide a convenient and elegant way to add functionality to functions and methods in Python. Decorators can be implemented in a number of different ways. One useful use-case for decorators involves using them with methods defined in a class. Decorating methods in the classes we create can extend the functionality of the defined method. For example, we could implement a data integrity check, or write the output of the method call to a file. It really is up to us what we choose to do. Method decorators simply provide us with the facility to extend functionality in an elegant way.
As always, the use of decorators with methods will be best demonstrated using an example.
In the example below, I have created a simple class called NumericalOps, which requires 2 arguments, val1, and val2. In the init constructor, these arguments get set as attributes in the object. Next, I have 2 methods defined in this class, namely, multiply_together and power.
Lets proposed the rather trivial and contrived scenario. I would like the two attributes, val1 and val2 set in the object to be numerical integers. The multiply_together method simply multiplies the two values together and returns the value from this operation. Importantly, in Python, it is possible to multiple a string, for example by an integer. This is demonstrated when we create an instance of the NumericalOps class, x, and pass in the two arguments, 2 and ‘my_string’, as shown in the console output in the example below.
To provide data integrity, I will write a decorator, named interger_check. To decorate a method in a class, first use the ‘@’ symbol followed by the name of the decorator function.
A decorator is simply a function that takes a function as an argument and returns yet another function. Here, when we decorate, multiply_together with integer_check, the integer function gets called. The multiply_together method gets passed as the argument to the integer_check function and returns…
Python Decorators – How to Create and Use Decorators in Python With Examples
Python decorators allow you to change the behavior of a function without modifying the function itself.
In this article I will show you how to create and use decorators. You will see how easy it is to use this advanced Python feature.
In this article I will discuss the following topics:
- When to use a decorator in Python
- Building blocks you use to create a decorator
- How to create a Python decorator
- Real-world examples of Python decorators
- Class decorators in Python
When to Use a Decorator in Python
You’ll use a decorator when you need to change the behavior of a function without modifying the function itself. A few good examples are when you want to add logging, test performance, perform caching, verify permissions, and so on.
You can also use one when you need to run the same code on multiple functions. This avoids you writing duplicating code.
Here are the building blocks used to create Python decorators
To get a better understanding of how decorators work, you should understand a few concepts first.
- A function is an object. Because of that, a function can be assigned to a variable. The function can be accessed from that variable.
def my_function(): print('I am a function.') # Assign the function to a variable without parenthesis. We don't want to execute the function. description = my_function
# Accessing the function from the variable I assigned it to. print(description()) # Output 'I am a function.'
2. A function can be nested within another function.
def outer_function(): def inner_function(): print('I came from the inner function.') # Executing the inner function inside the outer function. inner_function()
outer_function() # Output I came from the inner function.
Note that the inner_function is not available outside the outer_function . If I try to execute the inner_function outside of the outer_function I receive a NameError exception.
inner_function() Traceback (most recent call last): File "/tmp/my_script.py", line 9, in inner_function() NameError: name 'inner_function' is not defined
3. Since a function can be nested inside another function it can also be returned.
def outer_function(): '''Assign task to student''' task = 'Read Python book chapter 3.' def inner_function(): print(task) return inner_function homework = outer_function()
homework() # Output 'Read Python book chapter 5.'
4. A function can be passed to another function as an argument.
def friendly_reminder(func): '''Reminder for husband''' func() print('Don\'t forget to bring your wallet!') def action(): print('I am going to the store buy you something nice.')
# Calling the friendly_reminder function with the action function used as an argument. friendly_reminder(action) # Output I am going to the store buy you something nice. Don't forget to bring your wallet!
How to Create a Python Decorator
To create a decorator function in Python, I create an outer function that takes a function as an argument. There is also an inner function that wraps around the decorated function.
Here is the syntax for a basic Python decorator:
def my_decorator_func(func): def wrapper_func(): # Do something before the function. func() # Do something after the function. return wrapper_func
To use a decorator ,you attach it to a function like you see in the code below. We use a decorator by placing the name of the decorator directly above the function we want to use it on. You prefix the decorator function with an @ symbol.
@my_decorator_func def my_func(): pass
Here is a simple example. This decorator logs the date and time a function is executed:
from datetime import datetime def log_datetime(func): '''Log the date and time of a function''' def wrapper(): print(f'Function: \nRun on: ') print(f'') func() return wrapper @log_datetime def daily_backup(): print('Daily backup job has finished.') daily_backup() # Output Daily backup job has finished. Function: daily_backup Run on: 2021-06-06 06:54:14 ---------------------------
How to Add Arguments to Decorators in Python
Decorators can have arguments passed to them. To add arguments to decorators I add *args and **kwargs to the inner functions.
- *args will take an unlimited number of arguments of any type, such as 10 , True , or ‘Brandon’ .
- **kwargs will take an unlimited number of keyword arguments, such as count=99 , is_authenticated=True , or name=’Brandon’ .
Here is a decorator with arguments:
def my_decorator_func(func): def wrapper_func(*args, **kwargs): # Do something before the function. func(*args, **kwargs) # Do something after the function. return wrapper_func @my_decorator_func def my_func(my_arg): '''Example docstring for function''' pass
Decorators hide the function they are decorating. If I check the __name__ or __doc__ method we get an unexpected result.
print(my_func.__name__) print(my_func.__doc__) # Output wrapper_func None
To fix this issue I will use functools . Functools wraps will update the decorator with the decorated functions attributes.
from functools import wraps def my_decorator_func(func): @wraps(func) def wrapper_func(*args, **kwargs): func(*args, **kwargs) return wrapper_func @my_decorator_func def my_func(my_args): '''Example docstring for function''' pass
Now I receive the output I am expecting.
print(my_func.__name__) print(my_func.__doc__) # Output my_func Example docstring for function
Example of a Python Decorator in Action
I have created a decorator that will measure memory and speed of a function.
We’ll use the decorator to test the performance list generation using four methods: range, list comprehension, append, and concatenation.
from functools import wraps import tracemalloc from time import perf_counter def measure_performance(func): '''Measure performance of a function''' @wraps(func) def wrapper(*args, **kwargs): tracemalloc.start() start_time = perf_counter() func(*args, **kwargs) current, peak = tracemalloc.get_traced_memory() finish_time = perf_counter() print(f'Function: ') print(f'Method: ') print(f'Memory usage:\t\t MB \n' f'Peak memory usage:\t MB ') print(f'Time elapsed is seconds: ') print(f'') tracemalloc.stop() return wrapper @measure_performance def make_list1(): '''Range''' my_list = list(range(100000)) @measure_performance def make_list2(): '''List comprehension''' my_list = [l for l in range(100000)] @measure_performance def make_list3(): '''Append''' my_list = [] for item in range(100000): my_list.append(item) @measure_performance def make_list4(): '''Concatenation''' my_list = [] for item in range(100000): my_list = my_list + [item] print(make_list1()) print(make_list2()) print(make_list3()) print(make_list4()) # Output Function: make_list1 Method: Range Memory usage: 0.000072 MB Peak memory usage: 3.693040 MB Time elapsed is seconds: 0.049359 ---------------------------------------- Function: make_list2 Method: List comprehension Memory usage: 0.000856 MB Peak memory usage: 3.618244 MB Time elapsed is seconds: 0.052338 ---------------------------------------- Function: make_list3 Method: Append Memory usage: 0.000448 MB Peak memory usage: 3.617692 MB Time elapsed is seconds: 0.060719 ---------------------------------------- Function: make_list4 Method: Concatenation Memory usage: 0.000440 MB Peak memory usage: 4.393292 MB Time elapsed is seconds: 61.649138 ----------------------------------------
You can use decorators with classes as well. Let’s see how you use decorators with a Python class.
In this example, notice there is no @ character involved. With the __call__ method the decorator is executed when an instance of the class is created.
This class keeps track of the number of times a function to query to an API has been run. Once it reaches the limit the decorator stops the function from executing.
import requests class LimitQuery: def __init__(self, func): self.func = func self.count = 0 def __call__(self, *args, **kwargs): self.limit = args[0] if self.count < self.limit: self.count += 1 return self.func(*args, **kwargs) else: print(f'No queries left. All queries used.') return @LimitQuery def get_coin_price(limit): '''View the Bitcoin Price Index (BPI)''' url = requests.get('https://api.coindesk.com/v1/bpi/currentprice.json') if url.status_code == 200: text = url.json() return f"$" print(get_coin_price(5)) print(get_coin_price(5)) print(get_coin_price(5)) print(get_coin_price(5)) print(get_coin_price(5)) print(get_coin_price(5)) # Output $35968.25 $35896.55 $34368.14 $35962.27 $34058.26 No queries left. All 5 queries used.
This class will keep track of the state of the class.
Conclusion
In this article I talked about how to pass a function to a variable, nested functions, returning functions, and passing a function to another function as an argument.
I also showed you how to create and use Python decorators along with a few real-world examples. Now I hope that you will able to add decorators to your projects.