[Tutor] Fraction Class HELP ME PLEASE!

Quiles, Stephanie stephanie.quiles001 at albright.edu
Fri Aug 7 05:09:07 CEST 2015


Hello again Cameron, 

Thank you, your reply is most helpful. Responses and updated code below. Thoughts would be appreciated. The assignment has been sent as my deadline was 11pm EST. But i was able to implement your suggestions and tips right before i submitted. responses are below and here is the final code i submitted for grading. 


def gcd(m, n):
    while m % n != 0:
        oldm = m
        oldn = n
        m = oldn
        n = oldm % oldn
    return n

class Fraction:
    def __init__(self, top, bottom):
        self.num = top
        self.den = bottom

    def __str__(self):
        if self.den == 1:
            return str(self.num)
        if self.num == 0:
            return str(0)
        return str(self.num) + "/" + str(self.den)

    def simplify(self):
        common = gcd(self.num, self.den)
        self.num = self.num // common
        self.den = self.den // common

    def show(self):
        print(self.num, "/", self.den)

    def __add__(self, otherfraction):
        newnum = self.num * otherfraction.den + self.den * otherfraction.num
        newden = self.den * otherfraction.den
        common = gcd(newnum, newden)
        return Fraction(newnum // common, newden // common)

    def __iadd__(self, otherfraction):
        if isinstance(otherfraction):
            return self__iadd__(otherfraction)

    def __sub__(self, otherfraction):
        newnum = self.num * otherfraction.den - self.den * otherfraction.num
        newden = self.den * otherfraction.den
        common = gcd(newnum, newden)
        return Fraction(newnum // common, newden // common)

    def __mul__(self, otherfraction):
        newnum = self.num * otherfraction.num * self.den * otherfraction.den
        newden = self.den * otherfraction.den
        common = gcd(newnum, newden)
        return Fraction(newnum // common, newden // common)

    def __imul__(self, otherfraction):
        if isinstance(otherfraction):
            return self__mul__(otherfraction)

    def __div__(self, otherfraction):
        newnum = self.num * otherfraction.num / self.den * otherfraction.den
        newden = self.den * otherfraction.den
        common = gcd(newnum, newden)
        return Fraction(newnum // common, newden // common)

    def __truediv__(self, otherfraction):
        newnum = self.num * otherfraction.num // self.den * otherfraction.den
        newden = self.den * otherfraction.den
        common = gcd(newnum, newden)
        return Fraction(newnum // common, newden // common)

    def __pow__(self, otherfraction):
        newnum = self.num * otherfraction.num ** self.den * otherfraction.den
        newden = self.den * otherfraction.den
        common = gcd(newnum, newden)
        return Fraction(newnum // common, newden // common)

    def __radd__(self, otherfraction):
        newnum = self.num * otherfraction.num // self.den * otherfraction.den
        newden = self.den * otherfraction.den
        common = gcd(newnum, newden)
        return Fraction(newnum // common, newden // common)




    def getNum(self):
        return self.num

    def getDen(self):
        return self.den

    def __gt__(self, otherfraction):
        return (self.num * self.den) > (otherfraction.num * otherfraction.den)

    def __lt__(self, otherfraction):
        return (self.num * self.den) < (otherfraction.num * otherfraction.den)

    def __eq__(self, otherfraction):
        return (self.num * self.den) == (otherfraction.num * otherfraction.den)

    def __ne__(self, otherfraction):
        return (self.num * self.den) != (otherfraction.num * otherfraction.den)

    def __mod__(self, otherfraction):
        return (self.num * self.den) % (otherfraction.num * otherfraction.den)

    def __rshift__(self, otherfraction):
        return (self.num * self.den) >> (otherfraction.num * otherfraction.den)







""" This program will test the following operators, addition, subtraction, multiplication,
division, true division, exponentiation, radd, modulo, shifting, ordering (less than, greater , equal to, different than,
same as). All using fractions. """
def main():
    F1 = Fraction(1,2)
    F2 = Fraction(2,3)
    print("F1 = ", F1)
    print("F2 = ", F2)
    print("Add Fractions: F1 + F2=", Fraction.__add__(F1, F2))
    print("Subtract Fractions: F1 - F2=", Fraction.__sub__(F1, F2))
    print("Multiply Fractions: F1 * F2=", Fraction.__mul__(F1, F2))
    print("Division with Fractions: F1 / F2=", Fraction.__div__(F1, F2))
    print("True Division with Fractions: F1 / F2=", Fraction.__truediv__(F1, F2))
    print("Exponentiation with Fractions: F1 ** F2=", Fraction.__pow__(F1, F2))
    print("Is F1 Greater than F2?:", Fraction.__gt__(F1, F2))
    print("Is F1 less than F2?:", Fraction.__lt__(F1, F2))
    print("Is F1 Equal to F2?:", Fraction.__eq__(F1, F2))
    print("Is F1 different than F2?:", Fraction.__ne__(F1, F2))
    print("Radd:", Fraction.__radd__(F1, F2))
    print("Modulo of F1 and F2(this prints the remainder):", Fraction.__mod__(F1, F2))
    print("Rshift( returns F1 shifted by F2:", Fraction.__rshift__(F1, F2))


if __name__ == '__main__':
    main()

> On Aug 6, 2015, at 9:51 PM, Cameron Simpson <cs at zip.com.au> wrote:
> 
> On 06Aug2015 23:50, Quiles, Stephanie <stephanie.quiles001 at albright.edu> wrote:
>> thanks Cameron! Here is what i have so far… new question… how do
>> i test the iadd, imul, etc. operators?
> 
> Like the others, by firing them. You test __add__ by running an add between two expressions:
> 
> F1 + F2
> 
> You test __iadd__ by running the augmented add operation:
> 
> F1 += F2
> 
> and so forth. The "i" probably comes from the word "increment" as the commonest one of these you see is incrementing a counter:
> 
> count += 1
> 
> They're documented here:
> 
> https://docs.python.org/3/reference/datamodel.html#object.__iadd__
> 
> The important thing to note is that they usually modify the source object. So:
> 
> F1 += F2
> 
> will modify the internal values of F1, as opposed to __add__ which returns a new Fraction object.
> 
>> Hopefully this time the
>> indents show up.
> 
> Yes, looks good.
> 
>> And yes the beginning code came out of our text
>> book I added on some functions myself but they give you a big chunk
>> of it.
> 
> I thought it looked surprisingly complete given your questions. It's good to be up front about that kind of thing. Noone will think less of you.
> 
>> I am hoping that this is what the professor was looking for.
>> I want to add some more operators but am unsure how to put them in
>> or test them? For example I want to add __ixor__, itruediv, etc.
>> Any other suggestions would be great!
> 

> Adding them is as simple as adding new methods to the class with the right name, eg:
> 
>   def __iadd__(self, other):
>       ... update self by addition of other ...
> 
> If you want to be sure you're running what you think you're running you could put print commands at the top of the new methods, eg:
> 
>   def __iadd__(self, other):
>       print("%s.__iadd__(%s)..." % (self, other))
>       ... update self by addition of other ...
> 
> Obviously you would remove those prints once you were satisfied that the code was working.
> 
> Adding __itruediv__ and other arithmetic operators is simple enough, but defining __ixor__ is not necessarily meaningful: xor is a binary operation which makes sense for integers. It needn't have a natural meaning for fractions. When you define operators on an object it is fairly important that they have obvious and natural effects becauase you have made it very easy for people to use them.  Now, you could _define_ a meaning for xor on fractions, but personally that is one I would not make into an operator; I would leave it as a normal method because I would want people to think before calling it.
> 
> The point here being that it is generally better for a program to fail at this line:
> 
> a = b ^ c     # XOR(b, c)
> 
> because "b" does not implement XOR than for the program to function but quietly compute rubbish because the user _thoght_ they were XORing integers (for example).
> 
> Added points: make your next reply adopt the interleaved style of this message, where you reply point by point below the relevant text. It makes discussions read like conversations, and is the preferred style in this list (and many other techincal lists) because it keeps the response near the source text. Hand in hand with that goes trimming irrelevant stuff (stuff not replied to) to keep the content shorter and on point.
> 
> Other random code comments:
> 
> [...snip: unreplied-to text removed here...]
>> def gcd(m, n):
>>   while m % n != 0:
>>       oldm = m
>>       oldn = n
>> 
>>       m = oldn
>>       n = oldm % oldn
>>   return n
> 
> It reads oddly to have a blank line in the middle of that loop. yes i 
> 
—Yes i think i put in that blank line in error i have corrected this in my code 
>> class Fraction:
>>   def __init__(self, top, bottom):
>>       self.num = top
>>       self.den = bottom
>> 
>>   def __str__(self):
>>       if self.den == 1:
>>           return str(self.num)
>>       elif self.num == 0:
>>           return str(0)
>>       else:
>>           return str(self.num) + "/" + str(self.den)
> 
> While your __str__ function will work just fine, stylisticly it is a little odd: you're mixing "return" and "elif". If you return from a branch of an "if" you don't need an "elif"; a plain old "if" will do because the return will prevent you reaching the next branch. So that function would normally be written in one of two styles:
> 
—I have gone ahead and changed it to look more like your first style suggestion.

> Using "return":
>   def __str__(self):
>       if self.den == 1:
>           return str(self.num)
>       if self.num == 0:
>           return str(0)
>       return str(self.num) + "/" + str(self.den)
> 
> or using "if"/"elif"/...:
> 
>   def __str__(self):
>       if self.den == 1:
>           s = str(self.num)
>       elif self.num == 0:
>           s = str(0)
>       else:
>           s = str(self.num) + "/" + str(self.den)
>       return s
> 
> For simple things like __str__ the former style is fine. For more complex functions the latter is usually better because your code does not bail out half way through - the return from the function is always at the bottom.
> 
>>   def simplify(self):
>>       common = gcd(self.num, self.den)
>> 
>>       self.num = self.num // common
>>       self.den = self.den // common
> 
> Again, I would personally not have a blank line in th middle of this function. 
—Corrected! 
> 
>>   def show(self):
>>       print(self.num, "/", self.den)
>> 
>>   def __add__(self, otherfraction):
>>       newnum = self.num * otherfraction.den + \
>>                self.den * otherfraction.num
>>       newden = self.den * otherfraction.den
>>       common = gcd(newnum, newden)
>>       return Fraction(newnum // common, newden // common)
> 
> Here is where the earlier discussion about __add__ versus __iadd__ comes into consideration. I would be definine __iadd__ here because it is closely related to __add__. It would look a lot like __add__ except that instead of returning a new Fraction it would overwrite .num and .den with the values for the new fraction.
> 
—I thought about moving iadd up but left it be. I have now moved it to be right under add.

> If Addition were complex (and fractional addition is near this border for me) I might define a "private" called ._add to compute the new numerator and denominator, and then define __add__ and __iadd__ in terms of it, untested example:
> 
>   def _add(self, otherfraction):
>       newnum = self.num * otherfraction.den + \
>                self.den * otherfraction.num
>       newden = self.den * otherfraction.den
>       common = gcd(newnum, newden)
>       return newnum // common, newden // common
> 
>   def __add__(self, otherfraction):
>       newnum, newden = self._add(otherfraction)
>       return Fraction(newnum, newden)
> 
>   def __iadd__(self, otherfraction):
>       newnum, newden = self._add(otherfraction)
>       self.num = newnum
>       self.den = newdem
> 
> You can see that this (a) shortens the total code and (b) guarentees that __add__ and __iadd__ perform the same arithmetic, so that they cannot diverge by accident.
> 
> The shared method _add() is a "private" method. In Python this means only that because its name begins with an underscore, other parts of the code (outside the Fraction class itself) are strongly discouraged from using it: you, the class author, do not promise that the method will not go away or change in the future - it is part of the arbitrary internal workings of your class, not something that others should rely upon.
> 
> This is a common convention in Python - the language does not prevent others from using it. Instead we rely on authors seeing these hints and acting sensibly. By providing this hint in the name, you're tell other users to stay away from this method.
> 
>>   def getNum(self):
>>       return self.num
>> 
>>   def getDen(self):
>>       return self.den
> 
> These two methods are hallmarks of "pure" object oriented programming. A pure OO program never accesses the internal state of antoher object directly and instead calls methods like getNum() above to ask for these values. This lets class authors completely change the internals of a class without breaking things for others.
> 
> However, in Python it is more common to make the same kind of distinction I made earlier with the ._add() method: if an attribute like .num or .den does not have a leading underscore, it is "public" and we might expect other users to reach for it directly.
> 
> So we might expect people to be allowed to say:
> 
>   F1.num
> 
> to get the numerator, and not bother with a .getNum() method at all.
> 
> If there are other attributes which are more internal we would just name them with leaing underscores and expect outsiders to leave them alone.
> 
>>   def __gt__(self, otherfraction):
>>       return (self.num / self.den) > (otherfraction.num / otherfraction.den)
>> 
>>   def __lt__(self, otherfraction):
>>       return (self.num / self.den) < (otherfraction.num / otherfraction.den)
>> 
>>   def __eq__(self, otherfraction):
>>       return (self.num / self.den) == (otherfraction.num / otherfraction.den)
>> 
>>   def __ne__(self, otherfraction):
>>       return (self.num /self.den) != (otherfraction.num /otherfraction.den)
> 
> These looke like normal arithmetic comparison operators.
> 
> I notice that you're comparing fractions by division. This returns you a floating point number. Floating point numbers are _not_ "real" numbers.  Internally they are themselves implemented as fractions (or as scientific notation - a mantissa and an exponent - semanticly the same thing). In particular, floating point numbers are subject to round off errors.
> 
> You would be safer doing multiplecation, i.e. comparing:
> 
>   self.num * otherfraction.den == otherfraction.num * self.den

—i changed them all to show with the * 
> 
> which will produce two integers. Python uses bignums (integers of arbitrary size) and some thing should never overflow, and is _not subject to round off errors.
> 
> When you use division for the comparison you run the risk that two Fractions with very large denominators and only slightly different numerators might appear equal when they are not.
> 
>>   def __is__(self, otherfraction):
>>       return (self.num / self.den) is (otherfraction.num / otherfraction.den)
> 
> This is undesirable. There is no "__is__" method.

I thought i was doing something wrong when __is__ did not highlight. i was trying to do the identity (a is or is not b) is_(a, b). was not sure how to implement that or even if i could?
> 
> All the __name__ methods and attributes in Python are considered part of the language, and typically and implicitly called to implement things like operators (i.e. F1+F2 calls F1.__ad__(F2)).
> 
> You should not make up __name__ methods: they are reserved for the language.  There are at least two downsides/risks here: first that you think this will be used, when it will not - the code will never be run and you will wonder why your call does not behave as you thought it should. The second is that some furture update to the language will define that name and it will not mean what you meant when you used it. Now your code _will_ run, but not do what a user expects!
> 
> BTW, the jargon for the __name__ names is "dunder": .__add__ is a "dunder method"; derived from "double underscore", which I hope you'd agree is a cumbersome term.
> 
>> def main():
>>   F1 = Fraction(1,2)
>>   F2 = Fraction(2,3)
>>   print("F1 = ", F1)
>>   print("F2 = ", F2)
> 
> print() adds a space between the comma separate items, you don't need to include one inside the quotes.
> 
>>   print("Add Fractions: F1 + F2=", Fraction.__add__(F1, F2))
> 
> Any reason you've not fired this implicitly? Like this:

- wanted it to call the operator rather than to show the arithmetic in the code? thought it might get me brownie points? lol 
> 
>   print("Add Fractions: F1 + F2=", F1 + F2)
> 
> Actually, you can make a case for doing both in your main program to show that they do the same thing.
> 
>>   print("Subtract Fractions: F1 - F2=", Fraction.__sub__(F1, F2))
>>   print("Multiply Fractions: F1 * F2=", Fraction.__mul__(F1, F2))
>>   print("True Division with Fractions: F1 / F2=", Fraction.__truediv__(F1, F2))
>>   print("Exponentiation with Fractions: F1 // F2=", Fraction.__pow__(F1, F2))
> 
> Shouldn't this be "**”?

—yes i knew this but i copied and pasted code and forgot to change it. 
> 
>>   print("Is F1 Greater than F2?:", Fraction.__gt__(F1, F2))
>>   print("Is F1 less than F2?:", Fraction.__lt__(F1, F2))
>>   print("Is F1 Equal to F2?:", Fraction.__eq__(F1, F2))
>>   print("Is F1 different than F2?:", Fraction.__ne__(F1, F2))
>>   print ("Is F1 same as F2?:", Fraction.__is__(F1, F2))
> 
> Here you want to avoid this. Firstly, Python has an "is" operator, and it does not have a dunder method. Secondly, in what way does your __is__ differe from __eq__?
> 
>>   print("Is:", Fraction.__iadd__(F1, F2))
>> 
>> if __name__ == '__main__':
>>   main()
> 
> Otherwise this is all looking promising. Does it run correctly for you?

— yes all of them run except the iadd. i am working on it now! 
> 
> Cheers,
> Cameron Simpson <cs at zip.com.au>



More information about the Tutor mailing list