15-112 Fundamentals of Programming

Notes - Lecture 5.1


Content

  1. Intro examples
  2. Fraction example
    1. Constructor (__init__)
    2. Converting to strings
    3. Equality Testing
    4. Multiplying two fractions
    5. Using in Sets (__hash__ and __eq__)
    6. Using in Dictionaries (__hash__ and __eq__)
    7. The full Fraction example

Intro examples

For an introduction to OOP ideas and paradigms, check the slides and see the 3 simple examples below:

  • Rectangle class
  • Employee class
  • Cat class
  • Fractions example

    1. Constructor (__init__)

    class Fraction(object):
        def __init__(self, num, den):
            self.num = num
            self.den = den
    
    f1 = Fraction(2, 3)
    f2 = Fraction(1, 5)
    print(f1.num, f1.den)
    print(f2.num, f2.den)
    

    2. Converting to strings

    Defining a toString() method
    class Fraction(object):
        def __init__(self, num, den):
            self.num = num
            self.den = den
    
        def toString(self):
            return str(self.num) + "/" + str(self.den)
    
    f1 = Fraction(2, 3)
    f2 = Fraction(1, 5)
    print(f1.toString())
    print(f2.toString())
    
    The problem
    class Fraction(object):
        def __init__(self, num, den):
            self.num = num
            self.den = den
    
        def toString(self):
            return "%d/%d" % (self.num, self.den)
    
    f1 = Fraction(2, 3)
    f2 = Fraction(1, 5)
    print(f1.toString()) # prints 2/3
    print(f2.toString()) # prints 1/5
    
    # What about print(f1) or print(f2)?
    print(f1) # prints <__main__.Fraction object at 0x10611db38> (yuck!)
    print(f2) # prints <__main__.Fraction object at 0x10611dba8> (yuck!)
    
    The partial solution: __str__
    class Fraction(object):
        def __init__(self, num, den):
            self.num = num
            self.den = den
    
        def __str__(self):
            return "%d/%d" % (self.num, self.den)
    
    f1 = Fraction(2, 3)
    f2 = Fraction(1, 5)
    print(f1) # prints 2/3 
    print(f2) # prints 1/5 
    
    # What about adding a fraction object to a list and printing the list? 
    L = []
    L.append(f1)
    L.append(f2)
    print(L) # prints [<__main__.Fraction object at 0x106f68ba8>, 
             #         <__main__.Fraction object at 0x106f68be0>] (yuck!)
    
    The better solution: __repr__
    class Fraction(object):
        def __init__(self, num, den):
            self.num = num
            self.den = den
    
        def __repr__(self):
            return "%d/%d" % (self.num, self.den)
    
    f1 = Fraction(2, 3)
    f2 = Fraction(1, 5)
    
    # What about print(f1) or print(f2)?
    print(f1) # prints 2/3 
    print(f2) # prints 1/5 
    
    # What about adding a fraction object to a list and printing the list? 
    L = []
    L.append(f1)
    L.append(f2)
    print(L) # prints [2/3, 1/5]
    

    3. Equality Testing

    Comparing objects: defining our own method
    class Fraction(object):
        def __init__(self, num, den):
            self.num = num
            self.den = den
    
        def equal(self, other):
            return self.num == other.num and self.den == other.den
    
    f1 = Fraction(10, 2)
    f2 = Fraction(10, 2)
    print(f1.equal(f2)) # True
    
    # What about using == ?
    print(f1 == f2) # False! 
    
    The partial solution: __eq__
    class Fraction(object):
        def __init__(self, num, den):
            self.num = num
            self.den = den
    
        def __eq__(self, other):
            return self.num == other.num and self.den == other.den
    
    f1 = Fraction(10, 5)
    f2 = Fraction(10, 5)
    print(f1 == f2) # True
    
    # Can we compare objects from our own class to objects of other classes?
    print(f1 == 2) # Crashes!
    
    A better solution
    class Fraction(object):
        def __init__(self, num, den):
            self.num = num
            self.den = den
    
        def __eq__(self, other):
            if (isinstance(other, int)):
                return self.num/self.den == other
            elif (isinstance(other, Fraction)):
                return self.num == other.num and self.den == other.den
            return False
    
    f1 = Fraction(10, 5)
    f2 = Fraction(10, 5)
    print(f1 == f2) # True
    print(f1 == 2) # True
    

    4. Multiplying two fractions

    The problem
    class Fraction(object):
        def __init__(self, num, den):
            self.num = num
            self.den = den
    
    f1 = Fraction(1, 4)
    f2 = Fraction(2, 4)
    print(f1 * f2) # Crashes
    
    Using: __mul__
    class Fraction(object):
        def __init__(self, num, den):
            self.num = num
            self.den = den
    
        def __repr__(self):
            return "%d/%d" % (self.num, self.den)
    
        def __mul__(self, other):
            num = self.num*other.num
            den = self.den*other.den
            return Fraction(num, den)
    
    f1 = Fraction(1, 4)
    f2 = Fraction(2, 4)
    print(f1*f2) # prints 2/16 
    

    5. Using in Sets (__hash__ and __eq__)

    The problem
    class Fraction(object):
        def __init__(self, num, den):
            self.num = num
            self.den = den
    
    s = set()
    f1 = Fraction(1, 4)
    f2 = Fraction(1, 4)
    s.add(f1)
    print(f1 in s) # True
    print(f2 in s) # False (but 1/4 should be in the set!)
    
    The solution: __hash__ and __eq__
    class Fraction(object):
        def __init__(self, num, den):
            self.num = num
            self.den = den
    
        def __eq__(self, other):
            if (isinstance(other, int)):
                return self.num/self.den == other
            elif (isinstance(other, Fraction)):
                return self.num == other.num and self.den == other.den
            return False
    
        def __hash__(self):
            return hash((self.num, self.den))
    
    s = set()
    f1 = Fraction(1, 4)
    f2 = Fraction(1, 4)
    s.add(f1)
    print(f1 in s) # True
    print(f2 in s) # True
    

    6. Using in Dictionaries (__hash__ and __eq__)

    The problem
    class Fraction(object):
        def __init__(self, num, den):
            self.num = num
            self.den = den
    
    d = dict()
    f1 = Fraction(1, 4)
    f2 = Fraction(1, 4)
    d[f1] = 0.4
    print(d[f2]) # crashes: key-error! (But 1/4 should be in the dictionary)
    
    The solution (same as sets):
    class Fraction(object):
        def __init__(self, num, den):
            self.num = num
            self.den = den
    
        def __eq__(self, other):
            if (isinstance(other, int)):
                return self.num/self.den == other
            elif (isinstance(other, Fraction)):
                return self.num == other.num and self.den == other.den
            return False
    
        def __hash__(self):
            return hash((self.num, self.den))
    
    
    d = dict()
    f1 = Fraction(1, 4)
    f2 = Fraction(1, 4)
    d[f1] = 0.4
    print(d[f2]) # prints 0.4
    

    6. The full Fraction example

    The solution (same as sets):
    # Very simple, far-from-fully implemented Fraction class
    # to demonstrate the OOP ideas from above.
    # Note that Python actually has a full Fraction class that
    # you would use instead (from fractions import Fraction),
    # so this is purely for demonstrational purposes.
    
    def gcd(x, y):
        if (y == 0): return x
        else: return gcd(y, x%y)
    
    class Fraction(object):
        def __init__(self, num, den):
            # Partial implementation -- does not deal with 0 or negatives, etc
            g = gcd(num, den)
            self.num = num // g
            self.den = den // g
    
        def __repr__(self):
            return '%d/%d' % (self.num, self.den)
    
        def __eq__(self, other):
            if (isinstance(other, int)):
                return self.num/self.den == other
            elif (isinstance(other, Fraction)):
                return self.num == other.num and self.den == other.den
            return False
    
        def __mul__(self, other):
            if (isinstance(other, int)):
                return Fraction(self.num * other, self.den)
            else:
                return Fraction(self.num * other.num, self.den * other.den)
    
        def __hash__(self):
            return hash((self.num, self.den))
    
    def testFractionClass():
        print('Testing Fraction class...', end='')
        assert(str(Fraction(2, 3)) == '2/3')
        assert(str([Fraction(2, 3)]) == '[2/3]')
        assert(Fraction(2,3) == Fraction(2,3))
        assert(Fraction(2,3) != Fraction(2,5))
        assert(Fraction(2,3) != "Don't crash here!")
        assert(Fraction(2,3) * (Fraction(3,4)) == Fraction(1,2))
        assert(Fraction(2,3) * 5 == Fraction(10,3))
        s = set()
        assert(Fraction(1, 2) not in s)
        s.add(Fraction(1, 2))
        assert(Fraction(1, 2) in s)
        s.remove(Fraction(1, 2))
        assert(Fraction(1, 2) not in s)
        print('Passed.')
    
    if (__name__ == '__main__'):
        testFractionClass()
    


    Valid CSS! Valid XHTML 1.0 Strict