Lazy Loading in Python

Guide to lazy loading class attributes in Python

Madhumitha Kannan
4 min readOct 5, 2022

Lazy loading is a design pattern where we defer the loading of an object until it is needed. This is most commonly used to improve performance.

If you get data before and it’s never used, the time it took was wasted. Also, getting all your data up front can make the user think the application is slow or unresponsive at first. Deferring certain data/processing to lazy load lets your application focus on more immediate/important code.

source: https://crystallize.com/comics/image-please!

Lazy Loading class attributes in Python

Calculated attributes or attributes that are loaded using an expensive operation like an API call can be lazily loaded. We can perform lazy loading using @property of Python. The following example defines a class called Circle that has a radius attribute and area as a property.

import mathclass Circle:   def __init__(self, radius):      self.radius = radius   
@property
def area(self): print("Making expensive calculation...") return math.pi * self.radius ** 2
c = Circle(10)
print(c.__dict__)print(c.area)

Calling the __dict__ on the object returns only the radius as an attribute.

We can see that area is only computed when the property is accessed. This if a user loads an object and chooses not the use this attribute, it is not loaded unnecessarily.

Suppose we access the area property multiple times, it is recalculated every time — this is very inefficient.

print(c.area)
print(c.area)

Cached property

We can cache area and avoid this unnecessary recalculation:

import math
from functools import cached_property
class Circle: def __init__(self, radius): self.radius = radius
@cached_property
def area(self): print("Making expensive calculation...") return math.pi * self.radius ** 2
c = Circle(10)
print(c.area)
print(c.area)

Here, we can see that the calculation is performed only once. When we access area again, the value is returned from the cache.

Problems:

  1. When radius changes, area does not change as it is already cached.
  2. cached_property is writable. In our case, we don’t want the user to edit the area as it should be calculated from radius. User should only be able to modify radius.

Caching read-only properties using @property decorator

Steps:

  1. Make radius a property, and define a getter and setter. The setter should reset area to None, when radius changes. This way we are clearing the cache.
  2. Make area a property. Calculate the area only if it is None. This way, we will only calculate area once. Thus, avoiding recalculation every time area is accessed.
import mathclass Circle:     def __init__(self, radius):        self._radius = radius        self._area = None    @property    def radius(self):        return self._radius    
@radius.setter
def radius(self, value): if value != self._radius: self._radius = value self._area = None @property def area(self): if self._area is None: print("Making expensive calculation...") self._area = math.pi * self.radius ** 2 return self._areac = Circle(10)print(c.area)c.radius = 20print(c.area)print(c.area)

We can see that the calculation is performed only once. When the radius changes, the new area is calculated. Value is saved and returned for subsequent access of the area property.

Also, since we used @property decorator, area is now read-only.

This way, we can achieve lazy loading of attributes and improve the performance of loading computed properties. Lazy loading can be commonly observed in:

  1. Some frameworks and ORMs use lazy-loading when fetching table data that contain foreign key relationships. Here is an example from Django.
  2. Infinite scrolling, new content when the reader reaches the bottom of the page or close to it.
  3. Lazy loading images in browser viewport. A placeholder image is displayed in viewport until the user scrolls to the image.

References:

  1. https://realpython.com/python-property/#managing-attributes-in-your-classes
  2. https://www.pythontutorial.net/python-oop/python-readonly-property/
  3. https://www.sourcecodeexamples.net/2018/05/lazy-loading-pattern.html

--

--