Python decorator get or set dictionary value in class

Viewed 4034

I'm working on a class representing on object with numerous associated data. I'm storing these data in a dictionary class attribute called metadata. A representation could be:

{'key1':slowToComputeValue, 'key2':evenSlowerToComputeValue}

The calculating of the values is in some cases very slow, so what I want to do is, using "getter" functions, first try and get the value from the metadata dict. Only on a KeyError (i.e. when the getter tries to get a value for a key which doesn't exist yet) should the value be calculated (and added to the dictionary for fast access next time the getter is called).

I began with a simple:

try:
    return self.metadata[requested_key]
except KeyError:
    #Implementation of function

As there are many getters in the class, I started thought that these first 3 lines of code could be handled by a decorator. However I'm having problems making this work. The problem is that I need to pass the metadata dictionary from the class instance to the decorator. I've found several tutorials and posts like this one which show that it is possible to send a parameter to an enclosing function but the difficulty I'm having is sending a class instantiation attribute metadata to it (if I send a string value it works).

Some example code from my attempt is here:

def get_existing_value_from_metadata_if_exists(metadata):
    def decorator(function):
        @wraps(function)
        def decorated(*args, **kwargs):
            function_name = function.__name__
            if function_name in metadata.keys():
                return metadata[function_name]
            else:
                function(*args, **kwargs)
        return decorated
    return decorator

class my_class():
    @get_existing_value_from_metadata_if_exists(metadata)
    def get_key1(self):
        #Costly value calculation and add to metadata

    @get_existing_value_from_metadata_if_exists(metadata)
    def get_key2(self):
        #Costly value calculation and add to metadata

    def __init__(self):
        self.metadata = {}

The errors I'm getting are generally self not defined but I've tried various combinations of parameter placement, decorator placement etc. without success.

So my questions are:

  1. How can I make this work?
  2. Are decorators a suitable way to achieve what I'm trying to do?
2 Answers

Another way to go about this is to use class "properties" like so:

class MyClass():
    def __init__():
        self._slowToComputeValue = None
    @property
    def slowToComputeValue(self):
        if self._slowToComputeValue is None:
            self._slowToComputeValue = self.ComputeValue()
        return self._slowToComputeValue
    def ComputeValue(self):
        pass

Now you can access this as though it were a class attribute:

myclass = MyClass()
print(myclass.slowToComputeValue)
Related