0
def gcd(n, d):
        if n==0 or d==0:
            return 1
        while(n != d):
            if(n > d):
                n = n - d
            else:
                d = d - n
        return n

class Fraction:
    def __init__(self, n, d):
        self.num = int(n / gcd(abs(n), abs(d)))
        self.denom = int(d / gcd(abs(n), abs(d)))
        if self.denom < 0:
            self.denom = abs(self.denom)
            self.num = -1 * self.num
        elif self.denom == 0:
            raise ZeroDivisionError
    def __str__(self):
        if type(self) is tuple:
            if self[1] < 0:
                return '%s/%s' %(self[0], -1*self[1])
            else:
                return '%s/%s' %(self[0], self[1])
        elif self.denom == 1:
            return str(self.num)
        else:
            return '%s/%s' %(self.num, self.denom)

    def __add__(self, other):
        return self.num*other.denom + self.denom*other.num, self.denom*other.denom

    def __sub__(self, other):
        return self.num*other.denom - self.denom*other.num, self.denom*other.denom

    def __mul__(self, other):
        return self.num*other.num, self.denom*other.denom

    def __div__(self, other):
        return self.num*other.denom, self.denom*other.num

    def __eq__(self, other):
        if self.num == other.num and self.denom == other.denom:
            return "Equal"
        else:
            return "Not Equal"


f1 = Fraction(2, 4)
f2 = Fraction(4, 20)
add = Fraction.__add__(f1, f2)
sub = Fraction.__sub__(f1, f2)
eq = Fraction.__eq__(f1, f2)
mul = Fraction.__mul__(f1, f2)
div = Fraction.__div__(f1, f2)
print("Fraction one: "+str(Fraction.__str__(f1))+"\n"+"Second Fraction: "+str(Fraction.__str__(f2))+"\n"+"Add: "+str(Fraction.__str__(add))+"\n"+"Subtract: "+str(Fraction.__str__(sub))+"\n"+"Multiply: "+str(Fraction.__str__(mul))+"\n"+"Divide: "+str(Fraction.__str__(div))+Fraction.__str__(eq))

So my assignment requires me to make a fraction function but I keep on getting the following error for my code and I am not sure why:

Traceback (most recent call last):
  File "G:\Workshops\Week9.py", line 57, in <module>
    print("Fraction one: "+str(Fraction.__str__(f1))+"\n"+"Second Fraction: "+str(Fraction.__str__(f2))+"\n"+"Add: "+str(Fraction.__str__(add))+"\n"+"Subtract: "+str(Fraction.__str__(sub))+"\n"+"Multiply: "+str(Fraction.__str__(mul))+"\n"+"Divide: "+str(Fraction.__str__(div))+Fraction.__str__(eq))
  File "G:\Workshops\Week9.py", line 26, in __str__
    elif self.denom == 1:
AttributeError: 'str' object has no attribute 'denom'
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
NotHamzah
  • 29
  • 1
  • 6
  • 1
    You're returning a string from `__eq__` (which isn't good), then passing the string `eq` to `str` when you write `Fraction.__str__(eq)`. Why are you manually calling the `__` methods, and why are you returning a string from `__eq__`? Both of those problems are contributing to this error (although the manual calling of the magic methods is the bigger issue). – Carcigenicate Nov 27 '19 at 21:52
  • I am kind of new to programming but I have been told to show the output and they specifically wanted me to use "__ x __" to define the methods – NotHamzah Nov 27 '19 at 22:00
  • 3
    @NotHamzah yes, you should define them that way but never call them like that explicitly (outside very peculiar and particular circumstances). So, instead of ` Fraction.__add__(f1, f2)` you should use `f1 + f2` ... that's the *whole point*. Also, why on earth are you checking `if type(self) is tuple:` in your `__str__` implementation? Why would `self` ever be a `tuple`??? – juanpa.arrivillaga Nov 27 '19 at 22:14
  • @juanpa.arrivillaga I honestly didn't even know until now that you could just bs what `self` is. I'd never tried. This code messed with my head for a good couple minutes. I'd never seen `self` be anything other than the type of the enclosing class before. – Carcigenicate Nov 27 '19 at 22:22
  • @Carcigenicate yeah I mean, `SomeClass.func` will always return *just a normal function* which is what methods are. `self` is just a normal parameter, it is merely bound to the instance if called through an instance via the descriptor protocol, because function objects are descriptors! You can define `def foo(self): ...` outside a function, and then assign it to the class `SomeClass.foo = foo` and now it is a method that will work on instances, and again, `self` is merely a *convention* it is only important because it is the *first positional argument* – juanpa.arrivillaga Nov 27 '19 at 22:24
  • I understand that the instance is just always given as the first argument, and that the name `self` is convention, but I'd never tried to call an instance method as though it were a static method before with arguments other than an instance of that class. I thought that'd be an error. Apparently I need to review what's going on behind the scenes here. – Carcigenicate Nov 27 '19 at 22:27
  • @Carcigenicate they aren't "static methods", they are *just regular old functions*. I wrote [this answer](https://stackoverflow.com/a/55114843/5014455) that might be illuminating to another question a while back that shows exactly whats going on behind the scenes... The key is understanding descriptors and the descriptor protocol. EDIT and another answer that might be illuminating: https://stackoverflow.com/a/45645637/5014455 – juanpa.arrivillaga Nov 27 '19 at 22:28
  • @juanpa.arrivillaga Thanks. I think descriptors touch on some stuff that I've been meaning to look into for awhile now. I'll look over those links after I'm done my current project. Thanks. – Carcigenicate Nov 27 '19 at 22:39

1 Answers1

1

They want you to use __ to define the methods, but that's not how you use the methods.

__add__ for example is +. Instead of writing

add = Fraction.__add__(f1, f2)

Just write

add = f1 + f2

The same goes for all your other mathematical operators, as well as __str__ (which is what the str built-in uses).

Your main problem is that eq is a string*, and you're passing it to your __str__ method when you write

Fraction.__str__(eq)

Which means self inside of your __str__ is a string, not a Fraction, which means that self.denom is, as the error says, trying to get the denom attribute from the string self.

Since you have eq as a string, just change

Fraction.__str__(eq)

to just

eq

*Really though, __eq__ shouldn't be returning a string. It isn't disallowed, but it is bizarre and makes __eq__ not very useful. Just have

def __eq__(self, other):
    return self.num == other.num and self.denom == other.denom

Then change the fix above I mentioned to

str(eq)
Carcigenicate
  • 43,494
  • 9
  • 68
  • 117