15-112: Fundamentals of Programming and Computer Science
Class Notes: Object-Oriented Programming (OOP)
(Or, defining and using classes)



  1. Vocabulary
    You need to understand these terms, and in the case of specific methods, you need to know how and why to implement them.
    • struct, class, object, instance, type, isinstance
    • attribute, method, constructor, __dict__
    • __init__, __str__, __repr__, __eq__, __hash__
    Note that this is a subset of material from previous semesters. In particular, while you may wish to review those materials here, you do not need to know the following:
    • subclass, superclass, super, inherit, override
    • static method, @staticmethod, class method, @classmethod
    • __ne__, __add__, __radd__, __gt__, __ge__, __lt__, __le__, etc..

  2. Structs (download structClass.py)
    ######################################################
    # Structs (save in structClass.py (do not save in struct.py!))
    ######################################################
    
    # Here we use 'struct' in the classic sense:
    #   http://en.wikipedia.org/wiki/Struct_%28C_programming_language%29
    
    # Note that this is not the same as Python's struct module:
    #   https://docs.python.org/2/library/struct.html
    
    class Struct(object):
        def __init__(self, **kwargs):
            self.__dict__.update(kwargs)
    
        def __repr__(self):
            d = self.__dict__
            results = [type(self).__name__ + "("]  # or: self.__class__.__name__
            for key in sorted(d.keys()):
                if (len(results) > 1): results.append(", ")
                results.append(key + "=" + repr(d[key]))
            results.append(")")
            return "".join(results)
    
        def __eq__(self, other):
            return self.__dict__ == other.__dict__
    
        def __hash__(self):
            return hash(repr(self)) # inefficient but simple
    
    def testStructClass():
        print "Testing Struct class...",
    
        obj = Struct()
        obj.x = 42
        assert(obj.x == 42)
    
        obj = Struct(x=2, b=True, s="abc")
        assert((obj.x == 2) and (obj.b == True) and (obj.s == 'abc'))
        obj.x = 3
        assert(obj.x == 3)
        assert(str(obj) == "Struct(b=True, s='abc', x=3)") # alphabetical keys
        assert(str([obj]) == "[Struct(b=True, s='abc', x=3)]")
    
        obj2 = eval(repr(obj))
        assert(obj == obj2)
        assert(obj is not obj2) # they are equal, but not aliases
    
        s = set()
        assert(obj not in s)
        s.add(obj)
        assert(obj in s)
        assert(obj2 in s)       # since (obj == obj2)
        obj.x += 1              # obj is mutable, which is bad, because...
        assert(obj not in s)    # sigh...
    
        class Person(Struct): pass
        joe = Person(name="Joe", age=42)
        assert(str(joe) == "Person(age=42, name='Joe')")
    
        assert(type(joe) == Person)
        assert(isinstance(joe, Person) == True)
        assert(isinstance(joe, Struct) == True)
        print "Passed!"
    
    if __name__ == "__main__":
        testStructClass()
    

  3. Example: dotsDemo
    ######################################################
    # Struct Example:  dotsDemo
    ######################################################
    
    import eventBasedAnimation
    import random
    from structClass import Struct # defined above (saved in structClass.py)
    
    class Dot(Struct): pass
    
    def dotsDemoInitFn(data):
        data.dots = [ ]
        data.aboutText = data.windowTitle = "dotsDemo (click inside/outside dots)"
    
    def dotContainsPoint(dot, x, y):
        d = ((dot.x - x)**2 + (dot.y - y)**2)**0.5
        return (d <= dot.r)
    
    def dotsDemoMouseFn(event, data):
        for dot in reversed(data.dots):
            if (dotContainsPoint(dot, event.x, event.y)):
                dot.clickCount += 1
                return
        radius = random.randint(20,50)
        color = random.choice(["pink","orange","yellow","green","cyan","purple"])
        data.dots.append(Dot(x=event.x, y=event.y, r=radius,
                             fill=color, clickCount=0))
    
    def dotsDemoDrawFn(canvas, data):
        for dot in data.dots:
            canvas.create_oval(dot.x-dot.r, dot.y-dot.r,
                               dot.x+dot.r, dot.y+dot.r,
                               fill=dot.fill)
            canvas.create_text(dot.x, dot.y, text=str(dot.clickCount))
    
    eventBasedAnimation.run(
        initFn=dotsDemoInitFn,
        mouseFn=dotsDemoMouseFn,
        drawFn=dotsDemoDrawFn,
        )
    

  4. Example: betterDotsDemo (with methods, without Structs)
    ######################################################
    # betterDotsDemo (with methods, without Structs)
    ######################################################
    
    import eventBasedAnimation
    import random
    
    class Dot(object):
        def __init__(self, x, y):
            self.x = x
            self.y = y
            self.r = random.randint(20,50)
            self.fill = random.choice(["pink","orange","yellow","green",
                                       "cyan","purple"])
            self.clickCount = 0
    
        def containsPoint(self, x, y):
            d = ((self.x - x)**2 + (self.y - y)**2)**0.5
            return (d <= self.r)
    
        def draw(self, canvas):
            canvas.create_oval(self.x-self.r, self.y-self.r,
                               self.x+self.r, self.y+self.r,
                               fill=self.fill)
            canvas.create_text(self.x, self.y, text=str(self.clickCount))
    
    def betterDotsDemoInitFn(data):
        data.dots = [ ]
        data.aboutText = data.windowTitle = "betterDotsDemo (click in/outside dots)"
    
    def betterDotsDemoMouseFn(event, data):
        for dot in reversed(data.dots):
            if (dot.containsPoint(event.x, event.y)):
                dot.clickCount += 1
                return
        data.dots.append(Dot(event.x, event.y))
    
    def betterDotsDemoDrawFn(canvas, data):
        for dot in data.dots:
            dot.draw(canvas)
    
    eventBasedAnimation.run(
        initFn=betterDotsDemoInitFn,
        mouseFn=betterDotsDemoMouseFn,
        drawFn=betterDotsDemoDrawFn,
        )
    

  5. Example: bestDotsDemo (with Animation class)
    Requires eventBasedAnimation.py (version 1.10 or later)
    ######################################################
    # bestDotsDemo (with Animation class)
    ######################################################
    
    import eventBasedAnimation # requires version 1.10 or later
    import random
    
    class Dot(object):
        def __init__(self, x, y):
            self.x = x
            self.y = y
            self.r = random.randint(20,50)
            self.fill = random.choice(["pink","orange","yellow","green",
                                       "cyan","purple"])
            self.clickCount = 0
    
        def containsPoint(self, x, y):
            d = ((self.x - x)**2 + (self.y - y)**2)**0.5
            return (d <= self.r)
    
        def draw(self, canvas):
            canvas.create_oval(self.x-self.r, self.y-self.r,
                               self.x+self.r, self.y+self.r,
                               fill=self.fill)
            canvas.create_text(self.x, self.y, text=str(self.clickCount))
    
    class BestDotsDemo(eventBasedAnimation.Animation):
        def onInit(self):
            self.dots = [ ]
            self.aboutText = self.windowTitle = "bestDotsDemo (click in/out dots)"
    
        def onMouse(self, event):
            for dot in reversed(self.dots):
                if (dot.containsPoint(event.x, event.y)):
                    dot.clickCount += 1
                    return
            self.dots.append(Dot(event.x, event.y))
    
        def onDraw(self, canvas):
            for dot in self.dots:
                dot.draw(canvas)
    
    BestDotsDemo().run()
    

  6. Example: smileyClassDemo
    Requires eventBasedAnimation.py (version 1.10 or later)
    ######################################################
    # smileyClassDemo (similar to bestDotsDemo, but a bit more fun)
    ######################################################
    
    import eventBasedAnimation # requires version 1.10 or later
    import random
    
    #from Tkinter import *
    
    class Smiley(object):
        def __init__(self, x, y):
            self.x = x
            self.y = y
            self.r = random.randint(50,100)
            self.color = random.choice(["cyan", "yellow", "lightGreen", "pink",
                                        "orange", "green", "lightBlue", "purple"])
            self.maxSmileyPoints = 100
            self.smileyPoints = self.maxSmileyPoints  # the more points the happier
    
        def draw(self, canvas):
            (x, y, r) = (self.x, self.y, self.r)
            # face
            canvas.create_oval(x-r, y-r, x+r, y+r,
                               fill=self.color, outline="black", width=4)
            # eyes
            eyeY = y-r/3
            eyeR = r/6 
            for eyeX in [x-r/3, x+r/3]:
                canvas.create_oval(eyeX-eyeR, eyeY-eyeR, eyeX+eyeR, eyeY+eyeR,
                                   fill="black")
            # mouth
            mouthY1 = y+r/3
            mouthDy = r/6 # max height of mouth smile
            halfMax = self.maxSmileyPoints/2
            mouthY0 = mouthY1 - mouthDy*(self.smileyPoints - halfMax)/halfMax
            mouthY2 = mouthY0
            mouthDx = r*2/3
            for mouthX0 in [x-mouthDx, x+mouthDx]:
                canvas.create_line(mouthX0, mouthY0, x, mouthY1,
                                   fill="black", width=4)
    
        def contains(self, x, y):
            return ((self.x - x)**2 + (self.y - y)**2 <= self.r**2)
    
    class SmileyClassAnimationDemo(eventBasedAnimation.Animation):
        def onMouse(self, event):
            for smiley in reversed(self.smileys):
                if (smiley.contains(event.x, event.y)):
                    smiley.smileyPoints = smiley.maxSmileyPoints
                    return
            # clicked outside of all smileys, so make a new smiley
            newSmiley = Smiley(event.x, event.y)
            self.smileys.append(newSmiley)
    
        def onStep(self):
            for smiley in self.smileys:
                if (smiley.smileyPoints > 0):
                    smiley.smileyPoints -= 1
    
        def onInit(self):
            self.timerDelay = 100
            self.smileys = [ ]
    
        def drawSmileys(self, canvas):
            for smiley in self.smileys:
                smiley.draw(canvas)
    
        def drawText(self, canvas):
            texts = [self.smileyMessage,
                     "Click in Smileys to make them Smile!",
                     "Click outside Smileys to make a new Smiley!"
                    ]
            font = "Arial 20 bold"
            x = self.width/2
            for i in xrange(len(texts)):
                y = 25*(i+1)
                canvas.create_text(x, y, text=texts[i], font=font)
    
        def onDraw(self, canvas):
            self.drawSmileys(canvas)
            self.drawText(canvas)
    
    SmileyClassAnimationDemo(smileyMessage="Smile!", width=800, height=600).run()