How to Use Backing Properties (_foo_property) in the Initializer to achieve Lazy Loading of Python Properties

The aim of this page📝 is to explain the concept of Python properties with the focus on the use of backing attributes based on the particular example of a Python class for automating GCP IAM access configurations. The usecase is to print a URL of GCP Dashboard which involves a project_id. For that project_id, I need to make a networking call to Consul KV. However, I just want to call once, not each time the property is needed.

Pavol Kutaj
2 min readOct 25, 2023

Note: yes, naming sucks. backing property is a regular attribute/field in Python’s world and its concept is taken from elsewhere (for more, see On Backing Property in Kotlin and Java)

EXPLANATION

  • For more on properties in general see https://medium.com/p/3151c7131c04
  • In short, Python properties (the ones declared via @property decorator) are a way to define special methods that get called when an attribute is accessed (getter) or modified (setter).
  • These methods allow you to control how the attribute is accessed or modified, and they can include any logic you want.
  • A common use case for properties is to implement ‘lazy loading’, where the value of an attribute is not computed until it’s actually needed.
  • This can be useful for values that are expensive to compute, or that depend on some external resource (like a database or API).
  • In this case, you can store the value in a ‘backing attribute’ once it’s computed, and reuse this value in subsequent accesses to avoid unnecessary computations.
  • The backing attribute is usually a regular attribute with a similar name to the property, but prefixed with an underscore (e.g., _my_prop for a property my_prop).
  • This naming convention signals that the backing attribute is intended for internal use within the class, and should not be accessed directly from outside the class.
  • However, Python doesn’t enforce any access restrictions based on attribute names, so this is just a convention and not a hard rule.
  • When defining a property, you start by defining a method for the getter, and decorate it with @property.
  • This method should take no arguments (other than self), and return the value of the attribute.
  • If you want to define a setter for the property, you define another method with the same name, and decorate it with @<property_name>.setter.
  • This method should take one argument (in addition to self), which is the new value for the attribute.

CODE

Here is a particular example I have experienced:

class AccessConfig:
def __init__(self, client_name: str, env: str):
self.client_name = client_name
self.env = env
self._project_id = None

@property
def project_id(self):
if self._project_id is None:
self._project_id = get_consul_key_for_client(key=f"gcp_setup_{self.env}/output/project",
client=self.client_name)
return self._project_id

In this code, project_id is a property that's lazily loaded from an external source using get_consul_key_for_client. The value is stored in _project_id once it's retrieved, and reused in subsequent accesses.

LINKS

--

--

No responses yet