‘Big O’ in Python with decorator
We sometimes —always?, want to know how efficient our code is. So here comes the magical Big O notation:
Big O notation is used in computer science to describe the performance or complexity of an algorithm. Actually Big O notation is special symbol that tells you how fast an algorithm is. Of course you’ll use predefined algorithms often — and when you do, it’s vital to understand how fast or slow they are.
Efficiency covers lots of resources, including:
- CPU (time) usage
- Memory usage
- Disk usage
- Network usage
All are important but we will talk about CPU time: Time complexity or CPU usage. We can evaluate it as below:
#!/usr/bin/env python3
import datetime
def foo():
print("foo bar")
start_time = datetime.datetime.now()
foo()
finish_time = datetime.datetime.now()
duration = finish_time - start_time
print(f"Execution time: {duration}")
Is there any better method for it? Yes, with a decorator:
What are decorators in Python?
Python has an interesting feature called decorators to add functionality to an existing code.
This is also called metaprogramming
as a part of the program tries to modify
another part of the program at compile time.
Decorators allow us to wrap another function in order to extend the behavior of wrapped function, without permanently modifying it.
In decorators, functions are taken as the argument into another function and then called inside the wrapper function.
#!/usr/bin/env python3
import time
import random
import functools
def time_decorator(func):
""" Calculate the execution time of a method and return it back"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
duration = time.time() - start
print(f"Duration of {func.__name__} function was {duration}.")
return result
return wrapper
# with a function
@time_decorator
def sum_random_numbers(ran_num):
""" Return the sum between 0 and ran_num"""
total_sum = 0
for num in range(0, ran_num):
total_sum += num
return total_sum
# with a class instance method
class Klass:
@time_decorator
def sum_random_numbers(self, ran_num):
""" Return the sum between 0 and ran_num"""
total_sum = 0
for num in range(0, ran_num):
total_sum += num
return total_sum
if __name__ == "__main__":
# generate a random number between 1.000.000 and 2.000.000
ran_num = random.randint(1_000_000, 2_000_000)
print(f"random_number: {ran_num}")
sum_random_numbers(ran_num=ran_num)
Klass().sum_random_numbers(ran_num=ran_num)
If we run the code the output will be like:
random_number: 1123388
Duration of sum_random_numbers function was 0.05038046836853027.
Duration of sum_random_numbers function was 0.04922914505004883.
So we can use this decorator
to evaluate our efficiency everywhere in our
codes with ease.
Appendix
For more detail about decorators in Python: Decorators for Functions and Methods
Also I highly encourage to use python built-in
logging
method instead of
print
.
import logging
logger = logging.getLogger(__name__)
logger.info(f"Execution time: {self.func.__name__} function was {duration})