@functools.cached_property in Python

In Python, a cached_property functions as a decorator within the functools module. Its role is to convert a method within a class into a property. The unique aspect of this property lies in its ability to compute a value once and store it as a regular attribute throughout the instance’s existence.

Think of it like the property() function, but with an extra feature—caching. This becomes especially handy for handling expensive computed properties of instances that remain effectively immutable. Notably, the decorator comes into play only during lookups, and specifically when there’s no existing attribute with the same name. Refreshing the cached value is as simple as deleting the attribute, triggering the cached_property method to run once more.

Syntax

The signature for the cached_property decorator is as shown below.

@cached_property

Python @cached_property Examples:

Example 1:

In this scenario, we’re utilizing the decorator to compute the factorial of a number and display the time taken for each call. It’s evident that with each subsequent call to the same function, the execution time decreases.

from functools import cached_property
import time

# Function that computes Factorial with cached_property
class demo():
    
    def __init__(self, n):
        self.n=n
	
    #Using decorator
    @cached_property
    def factorial(self):
        fact=1
        for i in range(1,self.n+1):
            fact=fact*i
        print(fact)

#Computing factorial and printing time taken to execute
o=demo(5)	
starttime = time.time()
o.factorial
endtime = time.time()
print("Time taken to execute factorial with cached_property is", endtime-starttime)

#Computing factorial and printing time taken to execute 
o=demo(6)	
starttime = time.time()
o.factorial
endtime = time.time()
print("Time taken to execute factorial with cached_property is", endtime-starttime)

#Computing factorial and printing time taken to execute
o=demo(7)	
starttime = time.time()
o.factorial
endtime = time.time()
print("Time taken to execute factorial with cached_property is", endtime-starttime)

Output

120
Time taken to execute factorial with cached_property is 1.2159347534179688e-05
720
Time taken to execute factorial with cached_property is 4.5299530029296875e-06
5040
Time taken to execute factorial with cached_property is 3.5762786865234375e-06

Example 2:

In this instance, we are employing the decorator to calculate the sum of a series and showcasing the time taken for each call. It’s noticeable that with each successive call to the same function, the execution time decreases.

from functools import cached_property
import time

# Function that computes Sum of series with cache
class demo():
    
    def __init__(self, n):
        self.n=n
    
    #	Using decorator
    @cached_property
    def Sum(self):
        sum=0
        for i in range(1,self.n+1):
            sum=sum+i
        print(sum)

#Computing Sum of series and printing time taken to execute
o=demo(5)	
starttime = time.time()
o.Sum
endtime = time.time()
print("Time taken to execute Sum of series with cached_property is", endtime-starttime)

#Computing Sum of series and printing time taken to execute
o=demo(6)	
starttime = time.time()
o.Sum
endtime = time.time()
print("Time taken to execute Sum of series with cached_property is", endtime-starttime)

#Computing Sum of series and printing time taken to execute
o=demo(7)	
starttime = time.time()
o.Sum
endtime = time.time()
print("Time taken to execute Sum of series with cached_property is", endtime-starttime)

Output

15
Time taken to execute Sum of series with cached_property is 1.1444091796875e-05
21
Time taken to execute Sum of series with cached_property is 4.291534423828125e-06
28
Time taken to execute Sum of series with cached_property is 3.337860107421875e-06

Example 3:

In this scenario, we are utilizing the decorator to compute the Fibonacci series and displaying the time taken for each call. It is evident that with each successive call to the same function, the execution time decreases.

from functools import cached_property
import time

# Function that prints Fibonacci series with cached_property
class demo():
    
    def __init__(self, n):
        self.n=n
    
    #	Using decorator
    @cached_property
    def fibonacci(self):
        a = 0
        b = 1
        print(a, end=" ")
        print(b, end=" ")
        while(self.n>2):
            c=a+b
            a,b = b,c
            print(c, end=" ")
            self.n=self.n-1

#Displaying fibonacci series and printing time taken to execute
o=demo(10)	
starttime = time.time()
o.fibonacci
endtime = time.time()
print("Time taken to execute Fibonacci with cached_property is", endtime-starttime)

#Displaying fibonacci series and printing time taken to execute
o=demo(8)	
starttime = time.time()
o.fibonacci
endtime = time.time()
print("Time taken to execute Fibonacci with cached_property is", endtime-starttime)

#Displaying fibonacci series and printing time taken to execute
o=demo(13)	
starttime = time.time()
o.fibonacci
endtime = time.time()
print("Time taken to execute Fibonacci with cached_property is", endtime-starttime)

Output

0 1 1 2 3 5 8 13 21 34 Time taken to execute Fibonacci with cached_property is 2.0265579223632812e-05
0 1 1 2 3 5 8 13 Time taken to execute Fibonacci with cached_property is 8.58306884765625e-06
0 1 1 2 3 5 8 13 21 34 55 89 144 Time taken to execute Fibonacci with cached_property is 1.2159347534179688e-05

Conclusion

Therefore, starting from version 3.8 onward, incorporating this decorator results in faster execution by leveraging previously calculated values. This leads to a reduction in overall execution time.

References

Happy Learning 🙂