CMU 15-112: Fundamentals of Programming and Computer Science
Class Notes: Object-Oriented Programming (OOP) (part1)


  1. Optional Reading
  2. Type Testing (type, isinstance)
  3. Constructor (__init__)
  4. Equality Testing (__eq__)
  5. Converting to Strings (__str__ and __repr__)
  6. Using in Sets (__hash__ and __eq__)
  7. Using in Dictionaries (__hash__ and __eq__)
  8. Fraction Example

  1. Optional Reading
    For more on these topics, and many additional OOP-related topics, check here:
          https://docs.python.org/3/reference/datamodel.html

  2. Type Testing (type, isinstance)
    class A(object): pass a = A() print(type(a)) # A (technically, < class '__main__.A' >) print(type(a) == A) # True print(isinstance(a, A)) # True

  3. Constructor (__init__)
    class A(object): def __init__(self, color, isHappy): self.color = color self.isHappy = isHappy a1 = A('yellow', True) a2 = A('blue', False) print(a1.color, a1.isHappy) print(a2.color, a2.isHappy)

  4. Equality Testing (__eq__)
    The problem:
    class A(object): def __init__(self, x): self.x = x a1 = A(5) a2 = A(5) print(a1 == a2) # False!

    The partial solution: __eq__
    class A(object): def __init__(self, x): self.x = x def __eq__(self, other): return (self.x == other.x) a1 = A(5) a2 = A(5) print(a1 == a2) # True print(a1 == 99) # crash (darn!)

    A better solution:
    class A(object): def __init__(self, x): self.x = x def __eq__(self, other): return (isinstance(other, A) and (self.x == other.x)) a1 = A(5) a2 = A(5) print(a1 == a2) # True print(a1 == 99) # False (huzzah!)

  5. Converting to Strings (__str__ and __repr__)
    The problem:
    class A(object): def __init__(self, x): self.x = x a = A(5) print(a) # prints <__main__.A object at 0x102916128> (yuck!)

    The partial solution: __str__
    class A(object): def __init__(self, x): self.x = x def __str__(self): return "A(x=%d)" % self.x a = A(5) print(a) # prints A(x=5) (better) print([a]) # prints [<__main__.A object at 0x102136278>] (yuck!)

    The better solution: __repr__
    # Note: repr should be a computer-readable form so that # (eval(repr(obj)) == obj), but we are not using it that way. # So this is a simplified use of repr. class A(object): def __init__(self, x): self.x = x def __repr__(self): return "A(x=%d)" % self.x a = A(5) print(a) # prints A(x=5) (better) print([a]) # [A(x=5)]

  6. Using in Sets (__hash__ and __eq__)
    The problem:
    class A(object): def __init__(self, x): self.x = x s = set() s.add(A(5)) print(A(5) in s) # False

    The solution: __hash__ and __eq__
    class A(object): def __init__(self, x): self.x = x def __hash__(self): return hash(self.x) def __eq__(self, other): return (isinstance(other, A) and (self.x == other.x)) s = set() s.add(A(5)) print(A(5) in s) # True (whew!)

    A better (more generalizable) solution
    # Your getHashables method should return the values upon which # your hash method depends, that is, the values that your __eq__ # method requires to test for equality. class A(object): def __init__(self, x): self.x = x def getHashables(self): return (self.x, ) # return a tuple of hashables def __hash__(self): return hash(self.getHashables()) def __eq__(self, other): return (isinstance(other, A) and (self.x == other.x)) s = set() s.add(A(5)) print(A(5) in s) # True (still works!)

  7. Using in Dictionaries (__hash__ and __eq__)
    The problem (same as sets):
    class A(object): def __init__(self, x): self.x = x d = dict() d[A(5)] = 42 print(d[A(5)]) # crashes

    The solution (same as sets):
    class A(object): def __init__(self, x): self.x = x def getHashables(self): return (self.x, ) # return a tuple of hashables def __hash__(self): return hash(self.getHashables()) def __eq__(self, other): return (isinstance(other, A) and (self.x == other.x)) d = dict() d[A(5)] = 42 print(d[A(5)]) # works!

  8. Fraction Example
    # 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): return (isinstance(other, Fraction) and ((self.num == other.num) and (self.den == other.den))) def times(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).times(Fraction(3,4)) == Fraction(1,2)) assert(Fraction(2,3).times(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()