Skip to content

Lazy loading

Lazy loading is an programming implementation paradigm which delays the evaluation of an expression until its value is needed and which also avoids repeated evaluations.

Lazy evaluation is the preferred implementation when the operation is expensive, requiring either extensive processing time or memory. For example, in Python, one of the best-known techniques involving lazy evaluation is generators. Instead of creating whole sequences for the iteration, which can consume lots of memory, generators lazily evaluate the current need and yield one element at a time when requested.

Other example are attributes that take long to compute:

class Person:
    def __init__(self, name, occupation): = name
        self.occupation = occupation
        self.relatives = self._get_all_relatives()

    def _get_all_relatives():
        # This is an expensive operation

This approach may cause initialization to take unnecessarily long, especially when you don't always need to access Person.relatives.

A better strategy would be to get relatives when it's needed.

class Person:
    def __init__(self, name, occupation): = name
        self.occupation = occupation
        self._relatives = None

    def relatives(self):
        if self._relatives is None:
            self._relatives = ... # Get all relatives
        return self._relatives

In this case, the list of relatives is computed the first time Person.relatives is accessed. After that, it's stored in Person._relatives to prevent repeated evaluations.

A perhaps more Pythonic approach would be to use a decorator that makes a property lazy-evaluated.

def lazy_property(fn):
    '''Decorator that makes a property lazy-evaluated.
    attr_name = '_lazy_' + fn.__name__

    def _lazy_property(self):
        if not hasattr(self, attr_name):
            setattr(self, attr_name, fn(self))
        return getattr(self, attr_name)
    return _lazy_property

class Person:
    def __init__(self, name, occupation): = name
        self.occupation = occupation

    def relatives(self):
        # Get all relatives
        relatives = ...
        return relatives

This removes a lot of boilerplate, especially when an object has many lazily-evaluated properties.

Another approach is to use the getattr special method.