0

I a newbie in Python OOP and I have a problem with the below program. When I run it, it gives me an error AttributeError: 'MyClass' object has no attribute 'sum'. This problem can be fix easily by replace the line sum = self.sum at each function compute_sqrtSum(), compute_SumSquare() and compute_SumCube() by sum = self.compute_Sum(). But if so, every time the program run these three functions, it has to run compute_Sum() once, in total three times. So is there a way that I can access to sum but only run compute_Sum() once?

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

    def compute_Sum(self):
        sum      = self.x + self.y 
        self.sum = sum         
    
        return sum  

    def compute_sqrtSum(self):
        sum      = self.sum 
        sqrt_sum = sqrt(sum)

        return sqrt_sum 

    def compute_SumSquare(self):
        sum    = self.sum 
        sum_sq = sum * sum 

        return sum_sq 

    def compute_SumCube(self):
        sum    = self.sum 
        sum_cb = sum * sum * sum 

        return sum_cb   

user = MyClass(1, 2)
print(user.compute_sqrtSum())
print(user.compute_SumSquare())
print(user.compute_sqrtCube())
6
  • You will have run compute_Sum() before or inside the other methods. Commented Jul 24, 2021 at 7:36
  • @KlausD. So your mean I must run compute_Sum() more than once? Commented Jul 24, 2021 at 7:52
  • always put full error message (starting at word "Traceback") in question (not comment) as text (not screenshot, not link to external portal). There are other useful information. Commented Jul 24, 2021 at 8:11
  • 1
    use self.sum = self.x + self.y directly in __init__ and you will no need to use compute_Sum() Commented Jul 24, 2021 at 8:14
  • 1
    evetually in __init__ set self.sum = None and in other functions runs compute_Sum() only when self.sum is None Commented Jul 24, 2021 at 8:16

3 Answers 3

1

To have the attributes computed on-the-fly you could use properties to have a method called automatically to determine the value whenever it's needed. However this can become very slow if the value is accessed frequently either by users of the class or by the class itself if other methods within it also reference it.

A way to avoid that is to make the attributes "lazy" which means they aren't calculated until they're first referenced, but the value is cached so if it's needed again, the cached value is returned instead of the being re-calculated.

In the code below each method will only ever be run once because the lazy_property decorator—which isn't a property at all—has the side-effect of also creating an instance attribute of the same name as the class' property, which prevents it from being called again because of the way instance attributes are looked up in Python.

The similar but not the same as @furas' answer. It eliminates a lot of the repetitive code and also make it easy to apply the caching to other attributes as well, so they too, will never be calculated more than once.

Lazily-evaluated Property Pattern in Python. Jun 30, 2013. stevenloria.com. Licensed under CC-BY 4.0 License

def lazy_property(fn):
    """Decorator that makes a property lazy-evaluated."""
    attr_name = '_lazy_' + fn.__name__

    @property
    def _lazy_property(self):
        if not hasattr(self, attr_name):
            setattr(self, attr_name, fn(self))  # Create instance attribute.
        return getattr(self, attr_name)

    return _lazy_property


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

    @lazy_property
    def sum(self):
        return self.x + self.y

    @lazy_property
    def sqrtSum(self):
        return sqrt(self.sum)

    @lazy_property
    def SumSquare(self):
        return self.sum * self.sum

    @lazy_property
    def SumCube(self):
        return self.sum * self.sum * self.sum

Update

In Python 3.8 a cached_property decorator was added to the functools module which does basically the same thing as lazy_property above, so the code could simply be like this:

from functools import cached_property  # Requires Python 3.8+

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

    @cached_property
    def sum(self):
        return self.x + self.y

    @cached_property
    def sqrtSum(self):
        return sqrt(self.sum)

    @cached_property
    def SumSquare(self):
        return self.sum * self.sum

    @cached_property
    def SumCube(self):
        return self.sum * self.sum * self.sum


inst = MyClass(4, 2)
print(inst.sum)
print(inst.SumCube)
Sign up to request clarification or add additional context in comments.

Comments

0

Properties are your friends here.

class MyClass:
    def __init__(self, x, y):
        self.x = x 
        self.y = y 
    
    @property
    def sum(self):
       return self.x + self.y         
     
    @property
    def sqrt_sum(self):
        return sqrt(self.sum) 
   
    @property
    def sum_square(self):
        return self.sum * self.sum 

    @property
    def sum_cube(self):
        return self.sum * self.sum * self.sum 

Thus you could do

user = MyClass(1,2)
print(user.sum) # No parenthesis
print(user.sqrt_sum) # No parenthesis
print(user.sum_square) # No parenthesis
print(user.sum_cube)  # No parenthesis

By the way, you should use builtin names (here sum) with caution

5 Comments

this will run self.x + self.y many times. In sum_cube it will run it even 3 times.
Yes but if you don't want it you might run into trouble if you make the sum settable. Someone might change the value of self.sum and make the design inconsistent
yes, this is the good side - but running sum = compute_Sum() as in question will need less calculations :)
You should have inconstencies in mind while design. The sum of 2 number is so fast in python that this is not a big deal. In case of very consuming calculation there are other ways of dealing with it (caching...etc). But one should always avoid design issues.
you should describe it in your answer to explain OP why it is better.
0

You could calculate self.sum directly in __init__ and then you don't have to calculate it again

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

    def compute_sum(self):
        return self.sum

    def compute_sqrt_sum(self):
        return sqrt(self.sum)

    def compute_sum_square(self):
        #return self.sum * self.sum 
        return self.sum ** 2

    def compute_sum_cube(self):
        return self.sum ** 3

user = MyClass(1, 2)
print(user.compute_sqrt_sum())
print(user.compute_sum_square())
print(user.compute_sqrt_cube())

But if you change ie. user.x = 10 then it will use wrong sum.

So it can be good only if you don't want to change x,y,sum.


Eventually in __init__ you can set self.sum = None and calculate sum only when self.sum is None - so it would have to calculate it only once but it would have to always check if self.sum is None:

More or less like this

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

    def compute_sum(self):
        if self.sum is None:
            self.sum = self.x + self.y 
        return self.sum

    def compute_sqrt_sum(self):
        if self.sum is None:
            self.compute_sum()
        return sqrt(self.sum)

    def compute_sum_square(self):
        if self.sum is None:
            self.compute_sum()
        #return self.sum * self.sum 
        return self.sum ** 2

    def compute_sum_cube(self):
        if self.sum is None:
            self.compute_sum()
        return self.sum ** 3

user = MyClass(1, 2)
print(user.compute_sqrt_sum())
print(user.compute_sum_square())
print(user.compute_sqrt_cube())

But if you change ie. user.x = 10 then it will also use wrong sum.

So it can be good only if you don't want to change x,y,sum.

3 Comments

Your code is not really consistent. Your sum is settable. user=MyClass(1, 2); user.sum=20; user.compute_sqrt_sum() ==sqrt(20) != sqrt(3) because sum (in your case) is settable
all versions have good and bad sides. OP will have to choose which version it preferres
In case of libraries (which I do in daily basis), you quickly understand you should not play with those 'basic' rules. Otherwise you package will be unusable and lead to really weird results.. But yes you"re right, you choose your battle :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.