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 trialSerdar Halac
15,259 PointsWhy does this code re-set the self.value attribute from a string to an int?
So I answered the question fairly easily, but I still don't understand why the code was coded as such:
def iadd(self, other): self.value = self + other return self.value
when in init : def init(self, value): self.value = str(value)
It's clear that init sets the value attribute to a STRING (since this whole class is supposed to be a string representation of a number). But the iadd method calls the add method, which returns an int (return int(self) + other) or float, so that means that in turn the iadd method sets self.value, which as as I just pointed out was initialized to a STRING, to the result of the add function which is a FLOAT/INT. Doesn't that completely change the class' purpose entirely?
It gets weirder because I ran some of this code on sublime text and the Mac terminal and did:
numTest = NumString(8)
int(type(numTest.value))
print(type(numTest))
print(numTest)
which yielded:
<class 'str'>
<class '__main__.NumString'>
8
Which makes sense. Right? the value attribute is a class string, and the instance itself is an instance object of the NumString class.
But then continuing the code above and doing:
numTest += 7
print(numTest)
print(type(numTest.value))
It yields:
15
Traceback (most recent call last):
File "treetest5.py", line 44, in <module>
print(type(numTest.value))
AttributeError: 'int' object has no attribute 'value'
So ok, it sets numTest to numTest + 7 which is equal to 15, so thats what the new value attribute is. But then asking for the type of the value attribute, it no longer says class string! It now just says int object has no attribute value!
So it seems that the code provided by TreeHouse DOES change the class' purpose entirely, as far as I know (changing the value from a string representation of an int to a straight up int), but I don't know why. I also have no idea why print(type(numTest.value)) didn't just return <class 'int'>, and I really want to know!
So please help out, because I can pass the assignment and did, but have no clue why that's the way the Iadd and imul methods are supposed to be done. As far as I know they break the purpose of the class itself and such code would only be suitable for a class where the value itself is not supposed to be a string but an int.
Programming wisdom would be greatly appreciated.
class NumString:
def __init__(self, value):
self.value = str(value)
def __str__(self):
return self.value
def __int__(self):
return int(self.value)
def __float__(self):
return float(self.value)
def __add__(self, other):
if '.' in self.value:
return float(self) + other
return int(self) + other
def __radd__(self, other):
return self + other
def __iadd__(self, other):
self.value = self + other
return self.value
def __mul__(self, other):
if '.' in self.value:
return float(self) * other
return int(self) * other
def __rmul__(self, other):
return self * other
def __imul__(self, other):
self.value = self*other
return self.value
2 Answers
Chris Howell
Python Web Development Techdegree Graduate 49,702 PointsHi Serdar Halac
So I do see where you are confused here. It does look like there is in-fact a side effect with the code in this section. I think the overall take away Kenneth wanted you to have was how to use the the magic methods and what they went to.
But if you are curious of how to improve upon this class the problems are with the return of each of the magic method methods.
A few examples, we will only look at addition:
# Lets make NumString of 8.
ns1 = NumString(8)
Now we know, anytime we do ANYTHING with this class. At the end of it, we would like to keep our NumString class type. We dont want to lose it to another class type like int or str.
So lets start out with our print of ns1 from above.
# Print the type of ns1
print(type(ns1))
We should get...
>>> <class 'numstring.NumString'>
Because we haven't done anything except create the class.
Now we will perform some math and check our type again.
# Now lets do some math which should give us the issue.
ns1 += 8
# And re-print
print(type(ns1))
We should get...
>>> <class 'int'>
This seems confusing because its not what we expected back. But it is in-fact what we told the code to do if you look a bit closer.
When we called the +=
we really called the __iadd__
method on our class.
So lets go look at what is happening.
numstring.py
class NumString:
# ... other methods ...
def __init__(self, value):
self.value = str(value)
def __iadd__(self, other):
self.value = self + other
return self.value
When we say ns1 += 8
, it is shorthand for: ns1 = ns1 + 8
.
We are setting the variable ns1
to whatever is returned by the right side of the equals sign. Inside the __iadd__
method we can see that it is returning self.value
which has become an int type before we returned it.
So to fix the behavior of our NumString class, we have to change the return statements to our magic methods that affect this behavior to either return self
which would be considered mutable or a completely new instance of NumString which would be immutable. (Thanks Kenneth Love for your input!)
Your refactored __iadd__
method would look like this then:
class NumString:
# ... other methods ...
def __iadd__(self, other):
self.value = self + other
return NumString(self.value)
Try making that one change, then testing the ns1 += 8
and see what your type is.
Hope that helps, if not let me know.
Serdar Halac
15,259 PointsThank you so much. Really appreciate the effort to answer my question :) Clears it up.
Serdar Halac
15,259 PointsSerdar Halac
15,259 PointsThank you so much for your detailed and much better formatted answer! Really helps. And it seems to answer what the alternative would be to make it work as intended. Thanks.
One quick comment though, would recreating a new instance each time we increment the value property mean that the code would eventually lead to a lot of memory being used up by the program at once as it runs? Assuming it increments the variable a number of times? If for example we add a loop where the self.value gets added to itself, say, a million times, would that not create a million instances? Leading to memory leak issues? I know Python has automatic garbage disposal, so to speak, but it seems like those instances would keep existing until the code ends. Also when you say: "either return self which would be considered mutable or a completely new instance of NumString which would be immutable"
What is meant by this? Why would self and a new instance of NumString be different in terms of the mutability, since they're the same type of class instance? And why does returning a new instance imply immutability and self imply mutability?
Pardon my ignorance! Thank you for taking the time to answer.
Chris Howell
Python Web Development Techdegree Graduate 49,702 PointsChris Howell
Python Web Development Techdegree Graduate 49,702 PointsIn Python there is something called Garbage Collection, it goes quite more in-depth than this.
But to simply answer your question, once you have 0 references to an object, it will get garbage collected. So Python will automatically take care of free'ing that memory back up for you. But if for whatever reason you were remembering every reference to every newly created instance. Yes, you would eventually run out of memory. But even at that, it would take you awhile to do that today, memory isnt a huge issue today. It was in the early days!
As for the Mutable and Immutable The best way I can define it would be: Each instance of an object is holding its own "state" right. A mutable object can change that state and it will still remain the SAME object (by same object, I mean its the exact same object existing in the same spot in memory). However, an Immutable object is one that exists in memory and if its changed it will become a new address in memory. Even though its based off the old state, its a completely new object with new data.
In Python there are data types like this:
Lists, Sets, Dicts are mutable types
int, float, string, tuples are immutable types.
Chris Howell
Python Web Development Techdegree Graduate 49,702 PointsChris Howell
Python Web Development Techdegree Graduate 49,702 PointsSo if we change just two methods on NumString you can check what happens.
NOTE: I am using Python 3
then you can run this after you change these two.