SoFunction
Updated on 2024-10-30

Python Programming Boosts Performance with Lazy Attributes

Lazy loading is a programming paradigm that defers loading operations until it has to. Typically, inert summation is the preferred implementation when the operation has a high overhead and takes a lot of time or space. For example, in Python, one of the best-known techniques involving inert evaluation is the generator. Instead of creating an entire sequence for iteration, generators lazily generate one element at a time.

Outside of the Python world, many other object-oriented programming languages, such as Swift and Kotlin, have inert evaluations associated with objects. Specifically, you can specify that specific properties of a custom instance object are inert, meaning that they won't be created until they're explicitly accessed.

Why you need lazy loading

Before we get to the lazy attribute, some of you may be wondering why it's important, or why we're using the lazy attribute.

For example, in a social networking site, a feature is to view a person's followers in the form of a list. When we click on a user, we can view that user's profile in a pop-up window. The operation of obtaining user profile data can be expensive, requiring not only access to a remote server, but also storing the data in memory.

Then you can programmatically implement it by using the follower's profile as a lazy attribute, which is fetched only when a specific username is clicked.

That's why we need the lazy attribute.

How to use lazy loading

Method 1:

utilization@property

@property is a decorator that converts regular functions into properties, such as supporting dot notation access. So, strictly speaking, creating a property is not really creating a lazy property itself. Instead, it just provides an interface to simplify data handling. Let's start by looking at the code below.

class User:
    def __init__(self):
        self._profile_data = None
    @property
    def profile_data(self):
        if self._profile_data is None:
            print("Performing time-consuming operations...")
            self._profile_data = 'profile data'
        return self._profile_data
demo = User()
print("init done")
print(demo.profile_data)
#init done
# Perform time-consuming operations...
#profile data

After the initialization is done, no time-consuming operation will be performed, and the corresponding loading of the user list will not feel stuck. Only when getting the user's profile (click operation), the program will first determine whether the _profile_data already exists, and will only perform time-consuming operations if it doesn't, and if it does, it will return directly, which greatly improves the efficiency.

Method 2:

utilization__getattr__ Special methods

In Python, functions with double underscores around their names are called magic methods.__getattr__ can help us implement the lazy property.

For custom classes, the attributes of the instance object are stored in a dictionary, and you can access the instance object's__dict__ attribute is obtained. It is worth noting that if the__dict__ does not contain the specified attribute, Python will call the magic method__getattr__, write a code and you'll understand:

class User:
    def __init__(self):
        self._profile_data = None
         = 'None'
     def __getattr__(self, item):
        print("called __getattr__")
        if item == 'profile_data':
            if self._profile_data is None:
                print("Performing time-consuming operations...")
                self._profile_data = 'profile data'
            return self._profile_data
 user = User()
print("init done")
print(user.__dict__)
print(user.profile_data)
print(user.__dict__)
print()

The output is as follows:

init done
{'_profile_data': None, 'name': 'None'}
called __getattr__
perform a time-consuming operation...
profile data
{'_profile_data': 'profile data', 'name': 'None'}
None

As with method 1, no time-consuming operations are performed after initialization is complete, and when we fetch the profile_data property, since profile_data is not in the__dict__ in the program, so it will execute the__getattr__ method, and name in the__dict__ The name attribute will not be executed at all when fetching the__getattr__ Methods.

How can you tell if an attribute is in the__dict__ What about the attribute? As long as the attribute is not explicitly defined or setattr is used to set the attribute, it won't be in the__dict__ Center.

So it's possible to use magic methods__getattr__ to create the lazy attribute profile_data.

Note that Python also has a similar magic method__getattribute__, with__getattr__ method is called each time the property is retrieved.__getattribute__ Methods.

class User:
    def __init__(self):
        self._profile_data = None
         = 'None'
    def __getattribute__(self, item):
        print("called __getattr__")
user = User()
print("init done")
print(user.profile_data)
print()

The program output is as follows:

init done
called __getattr__
None
called __getattr__
None

This function is only useful if you expect properties to change very frequently and only the latest data is relevant. In these cases, we can achieve the effect by defining a correlation function. In other words, I don't recommend you try to use it because it's easy to get into an infinite recursive loop.

final words

In this article, we focus on two practical ways to implement lazy properties in Python: one uses the@property decorator, another using the__getattr__ Special Methods.

Personally, I prefer to use the attribute decorator, it's more straightforward and easier to understand. However, when you need to define multiple lazy attributes, that getattr method is better because it provides a centralized place to manage those lazy attributes.

Above is the details of Python programming to improve performance through lazy attributes, for more information about Python lazy attributes to improve performance, please pay attention to my other related articles!