Search code examples
pythonclassoophasattr

Check whether class variable has been defined


I am dealing with a scenario where I have a python class Foo. Foo, among other things does many big calculations, which I would not do unless required. So, when I define the getter method for one of those big calculations, how do I make sure that the method corresponding to the calculation (here bigcalculation()) has already run?

class Foo:
    def __init__(self):
        #initialize some stuff
    def bigcalculation(self):
         #perform calculation
         self.big_calc_result=[big list of numbers];

    def get_big_calc_result(self):
        if hasattr(self,'big_calc_result')==False:
            self.bigcalculations();
        return sef.big_calc_result;

If its run once, I don't want it to run again. and I don't want caller to have to keep track of whether it has run once or not.

Now, I do it using hasattr() function as above, but I think this is a really ugly way to do it. Is there a more elegant pythonic way to do it?

An alternative I can think of, is to define, in my init() function, all the variables that I would ever use in the class, as empty list. Then check whether big_calc_result is an empty list or not to determine if self.bigcalculation() has already run. Is this a better approach?

related question:Python lets me define variables on the fly in a class. But is that bad programming practice?

Edit: In retrospect, I also found that using exceptions can also be another way of handling this situation. That might be a more pythonic way of doing things.

def get_big_calc_result(self):
    try:
        return self.big_calc_result;
    except AttributeError:
        self.bigcalculations();
        return self.big_calc_result;

The answers to this question are useful: Checking for member existence in Python


Solution

  • You can memoize the result and store it as a property:

    class Foo(object):
        def __init__(self):
            self._big_calc_result = None
    
        @property
        def big_calc_result(self):
            if self._big_calc_result is not None:
                return self._big_calc_result
            else:
                self._big_calc_result = self.big_calc_run()
                return self._big_calc_result 
    
        def big_calc_run(self):
            time.sleep(10)  # Takes a long time ...
    

    Now, you just initialize the class and get the result:

    f = Foo()
    x = f.big_calc_result
    y = f.bic_calc_result  # Look mom, this happened really quick
    

    Of course, you don't have to use a property if that is less intuitive and you can change things around here to suit the API you're trying to provide. The real meat is in caching the result in the variable prefixed with an underscore which is to say "This is an implementation detail -- if you mess with it, you deserve to have your code break at some point in the future".