liveBook · Manning

Lazy initialization

As explained in Python How-To

A lazy attribute is an attribute whose value is computed only when the attribute is accessed rather than when its object is initialized.

Suppose a followers list needs usernames, but each follower’s full profile requires a server request. Loading every profile while creating the list wastes time and memory if most profiles are never opened. Lazy initialization keeps object creation cheap and fetches a profile only when its value is requested. section 9.4.1 section 9.4

Cache a profile with a property

class User: def __init__(self, username): self.username = username self._profile_data = None @property def profile_data(self): if self._profile_data is None: self._profile_data = self._get_profile_data() return self._profile_data def _get_profile_data(self): print('Fetching profile') return 'Extensive profile data' user = User('John') # No fetch print(user.profile_data) # Fetching profile; Extensive profile data print(user.profile_data) # Extensive profile data The property preserves dot-notation access. Its internally managed value starts as None; the first access fetches and stores the data, and later accesses return the stored value. section 9.4.3

An alternative is to leave the attribute unset in __init__ and override __getattr__. When an absent attribute is requested, __getattr__ can fetch its value, save it with setattr, and return it. Do not initialize that attribute to None in this version: an existing entry prevents this fallback from running. The property approach is generally clearer because its behavior is explicit; __getattr__ depends on understanding attribute resolution. section 9.4.2 section 9.4.4

Listing 9.7 Overriding __getattr__ in a class
class User:
   def __init__(self, username):
       self.username = username
       print(f"### User {username} created")

   def __getattr__(self, item):
       print(f"__getattr__ called for {item}")
       if item == "profile_data":
           profile_data = self._get_profile_data()
           setattr(self, item, profile_data)
           return profile_data

   def _get_profile_data(self):
       # get the data from the server and load it into memory
       print("*** Run the expensive operation")
       fetched_data = "Extensive data, including thumbnail, 
 followers, etc."
       return fetched_data

Inspect the __getattr__ alternative: it fetches profile_data only when requested, then stores it so the next access does not fetch again. section 9.4.2

Use this pattern when constructing many objects would otherwise trigger expensive work that only some objects need. The same consideration can apply to costly database-backed information. section 9.4.1 section 9.5.3

How Python How-To covers it

How deep it goes
Works through two caching implementations and explains the simplified __dict__ and __getattr__ fallback; does not detail the full attribute-resolution order.
Examples
Shows followers whose remote profiles are fetched on first access, then reused on a second access.
What it assumes
Assumes familiarity with custom classes and @property, while explaining the relevant __dict__ and __getattr__ fallback.
What you can do after
Build it from scratch using a cached property or __getattr__, preferring the explicit property approach.
How it connects
Links lazy evaluation to generators, Python properties, __getattr__, instance attributes, and database operations.

Also explained in

Compare how 3 books explain lazy initialization →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Python How-To. The section links point to the parts of the book it draws on.