Lazy Loading in Python

10 May 2018

Lazy loading is a design pattern which defers resource initialization until needed. The resource loading often involves slow I/O operations, and results in memory consumption. Therefore, lazy loading could be efficient if the resources are not eagerly requested, especially when only part of the resources will be actually used in the program.

In the following sections, I will discuss about how to do lazy loading in Python.

An eager loading example

This is an eager loading example which loads all resources in the beginning.

def load(name):
    return 'Content of ' + name

class Eager(object):
    def __init__(self):
        self.__first = load('first')
        self.__second = load('second')

    @property
    def first(self):
        return self.__first

    @property
    def second(self):
        return self.__second

e = Eager()
print(e.first)
print(e.second)

Lazy loading example

In this example, the properties are only loaded when accessed the first time.

class Lazy1(object):
    def __init__(self):
        self.__first = None
        self.__second = None

    @property
    def first(self):
        if self.__first is None:
            self.__first = load('first')
        return self.__first

    @property
    def second(self):
        if self.__second is None:
            self.__second = load('second')
        return self.__second

lazy1 = Lazy1()
print(lazy1.first)
print(lazy1.second)

Use __getattr__

The previous example looks quite verbose. We may use __getattr__ to ease the life.

class Lazy2(object):
    def __init__(self):
        self.__data = {}

    def __getattr__(self, name):
        if name not in self.__data:
            self.__data[name] = load(name)
        return self.__data[name]

    def __setattr__(self, name, value):
        if not name.startswith('_'):
            raise AttributeError("can't set attribute")
        object.__setattr__(self, name, value)

lazy2 = Lazy2()
print(lazy2.first)
print(lazy2.second)

Lazy loading with dict like object

If you would like access the resources like an Python dict, it could be done by inheriting Mapping.

try:
    from collections.abc import Mapping
except ImportError:
    from collections import Mapping

class Lazy3(Mapping):
    def __init__(self):
        self.__data = {}

    def __getitem__(self, name):
        if name not in self.__data:
            self.__data[name] = load(name)
        return self.__data[name]

    def __iter__(self):
        return iter(self.__data)

    def __len__(self):
        return len(self.__data)

lazy3 = Lazy3()
print(lazy3['first'])
print(lazy3['second'])

The object is protected from direct modification. Mapping could be changed to MutableMapping if direct modification of the map is required.