Home > Enterprise >  Python simple lazy loading
Python simple lazy loading

Time:10-20

I'm trying to clean up some logic and remove duplicate values in some code and am looking for a way to introduce some very simple lazy-loading to handle settings variables. Something that would work like this:

FOO = {'foo': 1}
BAR = {'test': FOO['foo'] }

# ...complex logic here which ultimately updates the value of Foo['foo']...
FOO['foo'] = 2

print(BAR['test']) # Outputs 1 but would like to get 2

Update:

My question may not have been clear based on the initial responses. I'm looking to replace the value being set for test in BAR with a lazy-loaded substitute. I know a way I can do this but it seems unnecessarily complex for what it is, I'm wondering if there's a simpler approach.

Update #2:

Okay, here's a solution that works. Is there any built-in type that can do this out of the box:

FOO = {'foo': 1}

import types
class LazyDict(dict):
    
    def __getitem__(self, item):
        value = super().__getitem__(item)
        return value if not isinstance(value, types.LambdaType) else value()

BAR = LazyDict({ 'test': lambda: FOO['foo'] })

# ...complex logic here which ultimately updates the value of Foo['foo']...
FOO['foo'] = 2

print(BAR['test']) # Outputs 2

CodePudding user response:

As I stated in the comment above, what you are seeking is some of the facilities of reactive programming paradigm. (not to be confounded with the JavaScript library which borrows its name from there).

It is possible to instrument objects in Python to do so - I think the minimum setup here would be a specialized target mapping, and a special object type you set as the values in it, that would fetch the target value.

Python can do this in more straightforward ways with direct attribute access (using the dot notation: myinstance.value) than by using the key-retrieving notation used in dictionaries mydata['value'] due to the fact a class is already a template to a certain data group, and class attributes can define mechanisms to access each instance's attribute value. That is called the "descriptor protocol" and is bound into the language model itself.

Nonetheless a minimalist Mapping based version can be implemented as such:

FOO = {'foo': 1}

from collections.abc import MutableMapping

class LazyValue:
    def __init__(self, source, key):
        self.source = source
        self.key = key

    def get(self):
        return self.source[self.key]
    
    def __repr__(self):
        return f"<LazyValue {self.get()!r}>"


class LazyDict(MutableMapping):
    def __init__(self, *args, **kw):
        self.data = dict(*args, **kw)
        
    def __getitem__(self, key):
        value = self.data[key]
        if isinstance(value, LazyValue):
            value = value.get()
        return value

    def __setitem__(self, key, value):
        self.data[key] = value
    
    def __delitem__(key):
        del self.data[key]
    
    def __iter__(self):
        return iter(self.data)
    
    def __len__():
        return len(self.data)
    
    
    def __repr__():
        return repr({key: value} for key, value in self.items())


BAR = LazyDict({'test': LazyValue(FOO, 'foo')})
# ...complex logic here which ultimately updates the value of Foo['foo']...

FOO['foo'] = 2

print(BAR['test']) # Outputs 2

The reason this much code is needed is that there are several ways to retrieve data from a dictionary or mapping (.values, .items, .get, .setdefault) and simply inheriting from dict and implementing __getitem__ would "leak" the special lazy object in any of the other methods. Going through this MutableMapping approach ensure a single point of reading of the value in the __getitem__ method - and the resulting instance can be used reliably anywhere a mapping is expected.

However, notice that if you are using normal classes and instances rather than dictionaries, this can be much simpler - you can just use plain Python "property" and have a getter that will fetch the value. The main factor you should ponder is whether your referenced data keys are fixed, and can be hard-coded when writting the source code, or if they are dynamic, and which keys will work as lazy-references are only known at runtime. In this last case, the custom mapping approach, as above, will be usually better:

FOO = {'foo': 1}
class LazyStuff:
    def __init__(self, source):
        self.source = source
        
    @property
    def test(self):
        return self.source["foo"]


BAR = LazyStuff(FOO)
FOO["foo"] = 2
print(BAR.test)

Perceive that in this way you have to hardcode the key "foo" and "test" in the class body, but it is just plaincode, and no need for the intermediary "LazyValue" class. Also, if you need this data as a dictionary, you could add an .as_dict method to LazyStuff that would collect all attributes in the moment it were called and yield a snapshot of those values as a dictionary..

CodePudding user response:

You can try using lambdas and calling the value on return. Like this:

FOO = {'foo': 1}
BAR = {'test': lambda: FOO['foo'] }

FOO['foo'] = 2

print(BAR['test']()) # Outputs 2

CodePudding user response:

If you're only one level deep, you may wish to try ChainMap, E.g.,

>>> from collections import ChainMap
>>> defaults = {'foo': 42}
>>> myvalues = {}
>>> result = ChainMap(myvalues, defaults)
>>> result['foo']
42
>>> defaults['foo'] = 99
>>> result['foo']
99
  • Related