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 trialJames Estrada
Full Stack JavaScript Techdegree Student 25,866 PointsDoes Python try to convert any instance to an, e.g., int or float when doing math?
When we modified add to be:
def __add__(self, other):
if '.' in self.value:
return float(self) + other
return int(self) + other
Is python trying to convert NumString (self) to a float or int? I get the same result if I get self's value:
def __add__(self, other):
if '.' in self.value:
return float(self.value) + other
return int(self.value) + other
Here python would be converting a string (self.value) to a float or int right? Which one is the best approach?
1 Answer
Cooper Runstein
11,850 PointsThe dunder methods float and str are called when you do str(self) or float(self), and you've set them to point to self.value. I would use the first method because add will work no matter what you make str and float point to. If for some reason you decided to make the value of your class be self.value + something else, your add function will use a different value than if you were to simply check for the int value of your class, and that could create a bug that would be hard to solve.
James Estrada
Full Stack JavaScript Techdegree Student 25,866 PointsCould you give me an example where
If for some reason you decided to make the value of your class be self.value + something else, your add function will use a different value than if you were to simply check for the int value of your class, and that could create a bug that would be hard to solve.
would be a problem?
Cooper Runstein
11,850 PointsSure, say you're creating some sort of sales app, and you create a class to represent a product you're selling:
class Product:
def __init__(self, value, **kwargs):
self.value = value
for key, value in kwargs.items():
setattr(self, key, value)
def __int__(self):
return int(self.value)
def __add__(self, other):
if '.' in self.value:
return float(self.value) + other
return int(self.value) + other
#I can easily look up the cost of an object
car = Product('50000')
int(car)
#50000
#and I can add extras to the car:
fancy_paint = 500
car + fancy_paint # 50500
I'm only going to work with int for now, but float will function the same way for this.
Lets say that this functionality works great, but then we're told that we need the cost of our object to reflect the price of the item times the sales tax, but only if the place we're selling it in actually has a sales tax. So we use the **kwargs to pass in a tax, and alter the int method so when someone goes to look up the price they get the total cost of the item :
class Product:
def __init__(self, value, **kwargs):
self.value = value
for key, value in kwargs.items():
setattr(self, key, value)
def __int__(self):
try:
return int(self.value * self.tax)
except AttributeError:
return int(self.value)
def __add__(self, other):
if '.' in self.value:
return float(self.value) + other
return int(self.value) + other
This works great for looking up items, for instance:
# with a 10% sales tax
car = Product('50000', tax=1.1)
int(car) #55000
#with no sales tax
car = Product('50000')
int(car) #50000
But, the problem arises if we keep the add method the same, say a customer asks for the cost of a car in a place with 10% sales tax, as above, we tell them it's $55000. And then they ask how much fancy paint is and we tell them that's $500 (Pretend that has no sales tax for simplicity). When we punch it in we get a problem:
car = Product('50000', tax=1.1)
fancy_paint = 500.
car + fancy_paint #50500
We should have got back 55500, but because the add method was relying on self.value, not the returned number, we ended up with a different number on paper than from our program.
Now there are two possibilities, we could put the try/except block into the init, or we could go through and add a try/except block to add, the second one is probably not a good idea, because there are other dunder methods that we'd need to change as well.
The first option is a good one in this scenario, but not if it gets more complex, for instance what if we need to create a class that gets the sales tax free cost of the car so we can reduce the car when we put it on sale by a percentage of the non-taxed value? I don't want int or str to return this non-tax value, so I can't store self.value as the argument value * the argument tax, i'd need to create a taxed_value, and a non_taxed value, and that could get really messy the bigger the project.
def __init__(self, value, **kwargs):
self.base_value = value
for key, value in kwargs.items():
setattr(self, key, value)
try:
self.taxed_value = self.base_value * self.tax
except AttributeError:
self.taxed_value = self.base_value
def returnNotTaxed(self)
return self.base_value
This also gets much harder to troubleshoot, in a huge class, what if one single dunder method calls self.base_value instead of self.taxed_value?
Calling int or float on self is a good practice, because it will make sure the values externally match those that the dunder methods use. Even if int(Product) returns the wrong value, all the dunder methods will have the same bug, and when you find the issue, it can be resolved in one place rather than in every single dunder.
James Estrada
Full Stack JavaScript Techdegree Student 25,866 PointsIncredible explanation Cooper Runstein! I ran your code and I understood why I should use self instead of self.value.
I had to modify this in order to run correctly on my side:
def __int__(self):
try:
return int(int(self.value) * self.tax)
except AttributeError:
return int(self.value)
Thank you very much for taking your time to clarify to a stranger an issue that many have been wondering, but nobody has explained it as good as you!
Tyler Wells
Python Development Techdegree Student 2,678 PointsGreat example. And thank you for all of the details. I played with all of the code that Cooper provided and it all works like described. But, when said
but because the add method was relying on self.value, not the returned number, we ended up with a different number on paper than from our program.
if we take self.value and run it through the int() method why doesnt it then take our 50,000 value and multiply it by tax just like its supposed to in the int method and then return the 55,000?
Cooper Runstein
11,850 PointsTyler -- because self.value is just a property, it's just a special variable attached to an object in the type string. The int method has some default functionality when used on strings, for instance:
my_var = '50.5'
int(50.5) #50
The int method looks at the string, goes "okay, this is a string, I need to turn this into an integer". Simple enough, you probably knew that, and you probably realize there's a lot of code running under the hood to make this happen. However if I do something like:
class Product:
def __init__(self, value):
self.value = value
a = Product('50.5')
int(a)
Int takes in the object and gets confused because it's looking at an object that doesn't fit a type it knows how to deal with. So it'll throw a TypeError. int exists to fix that very problem, you're giving your class a way to act like a type that int understands. This you probably understand this too, so heres how it fits into your question -- if I run:
def __add__(self, other):
if '.' in self.value:
return float(self.value) + other
return int(self.value) + other
No matter what I do in the dunder int method, int is going to look at self.value and go "Ah, a string! I can handle this, here, I stripped the period and turned this into a number". In order to have int return anything other than the result of the default operation on the built in type string, it needs to see an object, and go "oh no, I can't handle this, let me send this off to the int method and let it do its thing". When you run int(self), you're triggering the int method, when you run int(self.value) you're triggering the built in functionality. In order to add a special feature, like multiplying my a tax value, you need to be triggering the int method.
Tyler Wells
Python Development Techdegree Student 2,678 PointsAwesome, thank you Cooper. Makes complete sense. Thank you very much for the help
Tyler Wells
Python Development Techdegree Student 2,678 PointsCooper Runstein sorry to bother but if I may ask one more... self
is referring to the instantiated object. self.value
is returning the value attribute correct. So when we just use self
how does python know we want the 50000 number instead of the 1.1 which was specified as the tax attribute. They're both attributes yet python immediately goes to that value of 50000 that we had defined as the value attribute. Thanks again!
Dave StSomeWhere
19,870 PointsDave StSomeWhere
19,870 PointsPlease show the class declaration.