Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pythonic way to store a method result as an attribute

Tags:

python

oop

class

I have a python class with a property that performs an operation on large arrays. What is the best way to store the result of the method after it first was calculated without redoing the operation every time the property is accessed?

For example:

class MyClass:
    def __init__(self, x, y):
        """Input variables are two large data arrays"""
        self.x = x
        self.y = y

    @property
    def calculate(self):
        """Some computationally expensive operation"""
        try:
            # the result has been calculated already
            return self._result
        except AttributeError:
            # if not, calculate it
            self._result = self.x * self.y
            return self._result

Usage:

>>> foo = MyClass(2,3)
>>> a = foo.calculate
>>> print(a)
6

As you can see, all I came up with so far is to have a 'hidden' attribute where the result is stored. Is there a better way to do this? Is the use of @property correct here?

Thanks.

like image 984
Denis Sergeev Avatar asked May 11 '16 22:05

Denis Sergeev


People also ask

What is __ get __ in Python?

Python __get__ Magic Method. Python's __get__() magic method defines the dynamic return value when accessing a specific instance and class attribute. It is defined in the attribute's class and not in the class holding the attribute (= the owner class).

What is __ Setattr __?

Python's magic method __setattr__() implements the built-in setattr() function that takes an object and an attribute name as arguments and removes the attribute from the object. We call this a “Dunder Method” for “Double Underscore Method” (also called “magic method”).

Which method will ensure that an attribute has been defined before you access it?

getattr() – This function is used to access the attribute of object. hasattr() – This function is used to check if an attribute exist or not. setattr() – This function is used to set an attribute.

What does @property do in Python?

The @property is a built-in decorator for the property() function in Python. It is used to give "special" functionality to certain methods to make them act as getters, setters, or deleters when we define properties in a class.


2 Answers

You need to remember the values for x and y that were used to calculate the cached result. If they change, you need to re-calculate and refresh the cache.

class MyClass:
    def __init__(self, x, y):
        """Input variables are two large data arrays"""
        self.x = x
        self.y = y
        self._calculate = None
        self._x = None
        self._y = None

    @property
    def calculate(self):
        if not self._calculate or self._x != self.x or self._y != self.y:
            self._calculate = self.x * self.y
            self._x = self.x
            self._y = self.x

        return self._calculate


>>> a = MyClass(2,3)
>>> print(a.calculate)
6
>>> a.x = 553
>>> print(a.calculate)
1659
like image 57
C14L Avatar answered Oct 17 '22 13:10

C14L


The way you've implemented your property is just fine. It works, and has near zero overhead.

As a thought experiment you could take advantage of a specific property of non-data descriptors. A non-data descriptor is similar to property (which is a data descriptor). A non-data descriptor is an object that defines a __get__ method only. When the instance has an attribute with the same name as a the non-data descriptor this overrides the non-data descriptor (think monkey patching functions). eg.

class cache:
    def __init__(self, func):
        self.func = func

    def __get__(self, obj, type_):
        if obj is None:
            return self.func
        value = self.func(obj)
        setattr(obj, self.func.__name__, value)
        return value

class MyClass:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @cache
    def calculate(self):
        print("calculating")
        return self.x + self.y

o = MyClass(1, 2)
print("first", o.calculate) # prints "calcutating" then "first 3"
print("second", o.calculate) # just prints "second 3"
del o.calculate
print("third", o.calculate) # prints "calcutating" then "third 3"
like image 28
Dunes Avatar answered Oct 17 '22 12:10

Dunes