Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Python Object-Oriented Python Advanced Objects Math

Philip Schultz
Philip Schultz
11,437 Points

When to use self.value when overloading

Can someone explain to me what is happening in this video? Why is Kenneth using 'self.value' in some cases and 'self' in others. How are we suppose to know when to use one or the other? For example, here is a piece of code from the video on using __add __ .

def __add__(self, other):
    if  '.' in self.value
        return float(self) + other
    return int(self) + other

The magic methods above the __add__ methods all use self.value. For example, here is the int method.

def __int__(self):
    return int(self.value)

How can you use int(self) if the instance is more complex? I'm assuming this is a simple example in the video and the class only has one attribute, but what if there are multiple attributes. Wouldn't you have to use self.value or self.paycheck or what attribute you wan't to do the math with?

Also, can someone explain how __radd__ is working? I thought the purpose of making it is because we are not able to add an instance of NumString to a number if it's located on the right of the equation. If that is the case why are we typing self + other and not other + self, with the instance on the right side. Is it implicitly calling __add__ somehow? Why don't we have to check the value is a float or an int like in __add__?

[MOD: added ` escapes on method names -cf]

3 Answers

Chris Freeman
MOD
Chris Freeman
Treehouse Moderator 68,457 Points

Good questions Philip!

In the following explanation keep in mind that self.value refers to the stored string value and self refers to the NumString object. The key to knowing which to use is to think about the context. Is the string value needed or the object itself needed? The answer is sometimes it doesn't matter.

In the __add__ method, addition usually returns a number, so the __add__ method needs to convert the stored string value to a number before adding to the other operand passed in. The simplest way would be to use the methods from this class since they have been written explicitly for this purpose. That is, the __int__ and __float__ methods return the appropriate numeric representation of the NumString instance for use in addition. Calling int(self) or float(self) will explicitly call the __int__ and __float__ method of the "self" object. Since both the __int__ and __float__ methods operate on a NumString object, an object must be passed to it.That is why self is used. The condition in the if statement is checking for a decimal point in the value, so it must use self.value to see the actual object value.

In the __int__ method, the object is to get back a numeric integer value. self.value is used to as the argument to int() so that the objects value can be converted from a string to an integer. If return int(self) had been used, it would trigger another call to __int__ causing an infinite loop. This recursive call to __int__ would end in an "RecursionError: maximum recursion depth exceeded while calling a Python object" Yikes!

In the NumString class, the __add__ method could have used int(self.value) instead of using int(self) since int(self) calls __int__(self) which returns int(self.value) anyway.

The main point of using the object reference in int(self) is that it is not always obvious which attribute should be used in the context of addition or exactly how a numeric value for an object should be created. Using int(self) will always call the __int__ method of the class which is explicitly written to provide the numeric context value. If the __add__ method converted the object to a numeric context in its own way it could possible differ from the process used in __int__ and would most certainly violate the DRY principle.

In the __radd__ method, by using self + other it triggers a call to __add__ with the same arguments since the "self" is now on the left side of the operator instead of the right side. In most cases, object addition is symmetrical (a + b == b + a), so it is effectively passing the task along to __add__ method. If other + self had been used in __radd__, an infinite loop would be created as other + self triggers another call to __radd__. Yikes, again!

Since __radd__ redirects the call to __add__, there is not need to check the if the value is a float or an int since it is covered in the __add__ method.

I hope I covered it all. Post back if you have more questions! Good luck!!

Iulia Maria Lungu
Iulia Maria Lungu
17,546 Points

I would like to add something to the great explanation of @Chris Freeman already gave because it made me understand things better. Regarding this:

Also, can someone explain how __radd__ is working? I thought the purpose of making it is because we are not able to add an instance of NumString to a number if it's located on the right of the equation. If that is the case why are we typing self + other and not other + self, with the instance on the right side. Is it implicitly calling __add__ somehow?

This would not work if the implemented methods were not __add__ and __radd__ but __sub__ and __rsub__, just because addition or multiplication are commutative mathematical operations but subtraction for instance is not. Below is what I did to have subtraction in place.

class NumString:
    def __init__(self, value):
        self.value = str(value)
    def __int__(self):
        return int(self.value)
    def __str__(self):
        return self.value
    def __add__(self, other):
        return int(self) + other
    def __radd__(self, other):
        return self + other
    def __sub__(self, other):
        return int(self) - other
    def __rsub__(self, other):
        return - (self - other)
Chris Freeman
Chris Freeman
Treehouse Moderator 68,457 Points

Great points! If needed you can also define __neg__ to be called when you have unary negation as in your last statement if needed.

    return - (result)

But since (self - other) above returns an int, the int.__neg__ would be run instead of NumString.__neg__.

Also be careful with Kenneth’s implementation of __iadd__. As written, it changes the object from NumSting to an int.

In the https://teamtreehouse.com/library/math video, the NumString.__iadd__ method uses self.value = self + other. Since self + other calls NumString.__add__, and it produces an integer result, this changes self.value from a stored string value to an integer value. So this instance's __iadd__ would only be called the first time. Also, it overwrites the class instance creating an integer in its place. Using self.value = str(self + other) would be more correct. Adding print statements to all of the scripted NumString methods yeilds:

>>> a = NumString(5)
__init__ run with value: 5
>>> type(a)
<class 'NumString.NumString'>
>>> a += 1
NumString5 running __iadd__ with other value: 1
NumString5 running __add__ with other value: 1
NumString5 running __int__
>>> a
6
>>> type(a)
<class 'int'>
Anthony Crespo
seal-mask
.a{fill-rule:evenodd;}techdegree
Anthony Crespo
Python Web Development Techdegree Student 12,973 Points

I'm no expert but maybe I can help.

self represents the instance of the class. By using the "self" keyword we can access the attributes and methods of the class in python.

Outside of a class you call an object by it's name.

Var_one + Var_two
Var_one.function()

But when you are writing a function inside your class the name of your variable is lost. That's why we call "self" and not the "Variable_name". So when you create the __ int__ function you write:

def __int__(self):
    return int(self.value)  # self.value equal to variable_name.value if it was outside "class"

When you create that function above inside your class, your class now have the possibility of been represented in an integer value. Without that function it doesn't know what to return if you want to turn your object in to an integer. It's the same thing for an object of type int. Inside that int class there is some functions that does the same to represent that int value in other variable type. But if you create a new type of variable like "Character" and want to convert it in to an int like this:

>>> variable = Character()  # variable type is Character
>>> int(variable)  # You pass a Character type object to an int

# You get this message error cause it doesn't know how to represent a Character in to an int.
TypeError: int() argument must be a string, a bytes-like object or a number, not 'Character'

TLDR : Inside your __ int__ function you return int(self.value) that represent a str type, a type int already know how to represent and inside your __ add__ function you return int(self) cause now it's possible to represent your object in to an int cause of that other function.