Computer Science 15-112, Fall 2012
Class Notes: 
Classes


  1. Required Reading
  2. Dictionaries vs Structs vs Classes
  3. Multiple Instances (One Dictionary Per Instance)
  4. Equality Testing (__eq__)
  5. Converting to Strings (__str__ and __repr__)
  6. Using in Sets and Dictionaries (__hash__ and __eq__)
  7. Class Variables (Data Attributes) and Class Methods
  8. Inheritance
    1. Specifying a Superclass
    2. isinstance vs type()
    3. Overriding methods
    4. Multiple Inheritance
  9. Old-Style vs New-Style Classes
  10. Test Methods
  11. Examples from class
    1. Animation With Classes
    2. Previous examples
      1. Text Adventure
      2. Fraction

Classes

  1. Required Reading
    1. The Python Language Reference, Ch 3 (Data Model)
    2. The Python Tutorial, Ch 9 (Classes)
       
  2. Dictionaries vs Structs vs Classes
    # Using a dictionary
    
    def dogYears(dog):
        return dog["age"]*7
    
    def printInfo(dog):
        print dog["name"]
        print dogYears(dog)
    
    myPet = dict()
    myPet["name"] = "marceau"
    myPet["age"] = 10
    
    printInfo(myPet)
    # Using a Struct
    
    def dogYears(dog):
        return dog.age*7
    
    def printInfo(dog):
        print dog.name
        print dogYears(dog)
    
    class Struct: pass
    myPet = Struct()
    myPet.name = "marceau"
    myPet.age = 10
    
    printInfo(myPet)
     
    # Using a class
    
    class Dog(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def dogYears(self):
            return self.age*7
    
        def printInfo(self):
            print self.name
            print self.dogYears()
    
    myPet = Dog("marceau", 10)
    myPet.printInfo()

     

  3. Multiple Instances (One Dictionary Per Instance)
    class Dog(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def dogYears(self):
            return self.age*7
    
        def printInfo(self):
            print self.name
            print self.dogYears()
    
    pet1 = Dog("marceau", 10)
    pet2 = Dog("fido", 5)
    
    print "You should not use instance.__dict__ to access"
    print "instance attributes, but in theory you sure could...!"
    
    print pet1.__dict__ # prints {'age': 10, 'name': 'marceau'}
    print pet2.__dict__ # prints {'age': 5, 'name': 'fido'}
  4. Equality Testing (__eq__)
    1. The problem
      class Dog(object):
          def __init__(self, name, age):
              self.name = name
              self.age = age
      
      pet1 = Dog("marceau", 10)
      pet2 = Dog("marceau", 10)
      print pet1 == pet2  # prints False (but we want True!)
    2. The solution:  Use __eq__
      class Dog(object):
          def __init__(self, name, age):
              self.name = name
              self.age = age
      
          def __eq__(self, other):
              return (self.name == other.name) and (self.age == other.age)
      
      pet1 = Dog("marceau", 10)
      pet2 = Dog("marceau", 10)
      print pet1 == pet2  # prints True (huzzah!)
  5. Converting to Strings (__str__ and __repr__)
    1. The (first) problem
      class Dog(object):
          def __init__(self, name, age):
              self.name = name
              self.age = age
      
          def __eq__(self, other):
              return (self.name == other.name) and (self.age == other.age)
      
      pet1 = Dog("marceau", 10)
      print pet1   # prints <__main__.Dog object at 0x0000000002308CC0>
    2. Failed solution #1:  Use __str__
      class Dog(object):
          def __init__(self, name, age):
              self.name = name
              self.age = age
      
          def __eq__(self, other):
              return (self.name == other.name) and (self.age == other.age)
      
          def __str__(self):
              return "Dog(%s, %s)" % (self.name, self.age)
      
      pet1 = Dog("marceau", 10)
      print pet1   # prints Dog(marceau, 10)  (huzzah!)
      print "That's great, but what about this:"
      print [pet1] # prints [<__main__.Dog object at 0x0000000002308CC0>]
    3. Failed Solution #2: Use __repr__
      class Dog(object):
          def __init__(self, name, age):
              self.name = name
              self.age = age
      
          def __eq__(self, other):
              return (self.name == other.name) and (self.age == other.age)
      
          def __repr__(self):
              return "Dog(%s, %s)" % (self.name, self.age)
      
      pet1 = Dog("marceau", 10)
      print pet1   # prints Dog(marceau, 10)
      print [pet1] # prints [Dog(marceau, 10)]  (huzzah!)
      
      print "That's great, but what about this (it is preferred"
      print "for eval(repr(x)) to return an object equal to x):"
      pet2 = eval(repr(pet1))  # NameError: name 'marceau' is not defined
      print pet2 == pet1
    4. Working Solution #3:  Use __repr__ with %r
      class Dog(object):
          def __init__(self, name, age):
              self.name = name
              self.age = age
      
          def __eq__(self, other):
              return (self.name == other.name) and (self.age == other.age)
      
          def __repr__(self):
              return "Dog(%r, %r)" % (self.name, self.age)
      
      pet1 = Dog("marceau", 10)
      print pet1   # prints Dog('marceau', 10)
      print [pet1] # prints [Dog('marceau', 10)]
      pet2 = eval(repr(pet1))
      print pet2 == pet1  # prints True (huzzah!)
  6. Using in Sets and Dictionaries (__hash__ and __eq__)
    1. The problem
      class Dog(object):
          def __init__(self, name, age):
              self.name = name
              self.age = age
      
          def __eq__(self, other):
              return (self.name == other.name) and (self.age == other.age)
      
      pet1 = Dog("marceau", 10)
      s = set()
      s.add(pet1)
      
      pet2 = Dog("marceau", 10)
      print pet2 == pet1 # prints True
      print pet1 in s    # prints True
      print pet2 in s    # prints False (should be True!)
    2. The solution (__hash__ and __eq__)
      class Dog(object):
          def __init__(self, name, age):
              self.name = name
              self.age = age
      
          def __eq__(self, other):
              return (self.name == other.name) and (self.age == other.age)
          
          def __hash__(self):
              # replace hashables tuple with instance data attributes for your own class
              hashables = (self.name, self.age)
              # Then just use the code below unmodified.
              # It is based on Bernstein's hash function, which is
              # simple enough but works well enough
              result = 0
              for value in hashables:
                  result = 33*result + hash(value)
              return hash(result)
      
      pet1 = Dog("marceau", 10)
      s = set()
      s.add(pet1)
      
      pet2 = Dog("marceau", 10)
      print pet2 == pet1 # prints True
      print pet1 in s    # prints True
      print pet2 in s    # prints True (huzzah!)
  7. Class Variables (Data Attributes) and Class Methods
    class Dog(object):
        dogCount = 0   # Class Variable (Data Attribute)
        
        @classmethod
        def getDogCount(cls):
            return Dog.dogCount
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
            Dog.dogCount += 1
    
    print Dog.getDogCount()  # prints 0
    pet1 = Dog("marceau", 10)
    print Dog.getDogCount()  # prints 1
    pet2 = Dog("marceau", 10)
    print Dog.getDogCount()  # prints 2
  8. Inheritance
    1. Specifying a Superclass
      class Monster(object):
          def sayBoo(self):
              print "boo!"
      
      class SeaMonster(Monster):
          def swim(self):
              print "glug glug!"
      
      m1 = SeaMonster()
      m2 = Monster()
      
      m1.sayBoo() # prints: boo! 
      m2.sayBoo() # prints: boo!
      
      m1.swim()   # prints: glug glug!
      m2.swim()   # AttributeError: 'Monster' object has no attribute 'swim' (as expected)
    2. isinstance vs type()
      class Monster(object):
          def sayBoo(self):
              print "boo!"
      
      class SeaMonster(Monster):
          def swim(self):
              print "glug glug!"
      
      m1 = SeaMonster()
      m2 = Monster()
      
      print type(m1) == Monster     # False
      print type(m2) == Monster     # True
      print type(m1) == SeaMonster  # True
      print type(m2) == SeaMonster  # False
      
      print isinstance(m1, Monster)    # True (differs from (type(m1) == Monster) above!)
      print isinstance(m2, Monster)    # True
      print isinstance(m1, SeaMonster) # True
      print isinstance(m2, SeaMonster) # False
    3. Overriding methods
      class Animal(object):
          def __init__(self, name):
              self.name = name
              print "Creating an Animal named", name
      
          def speak(self):
              print self.name + " says: '%s'" % self.getSpeakingSound()
              
          def getSpeakingSound(self):
              return "<generic animal sound>"
      
      class Dog(Animal):
          # override Animal's __init__, but still call it
          def __init__(self, name):
              # Call superclass's __init__ method
              super(Dog, self).__init__(name)
              # now do dog-specific things
              print "Creating a dog named", self.name
      
          # override Animal's getSpeakingSound entirely (do not call it)
          def getSpeakingSound(self):
              return "woof!"
      
      class Cat(Animal):
          # override Animal's __init__, but still call it
          def __init__(self, name):
              # Call superclass's __init__ method
              super(Cat, self).__init__(name)
              # now do cat-specific things
              print "Creating a cat named", self.name
      
          # override Animal's getSpeakingSound entirely (do not call it)
          def getSpeakingSound(self):
              return "meow!"
      
      animal1 = Dog("fred")      # prints: Creating an Animal named fred
                                 #         Creating a dog named fred
      animal2 = Cat("wilma")     # prints: Creating an Animal named wilma
                                 #         Creating a cat named wilma
      animal3 = Animal("barney") # prints: Creating an Animal named barney
      
      animal1.speak() # prints: fred says: 'woof!'
      animal2.speak() # prints: wilma says: 'meow!'
      animal3.speak() # prints: barney says: '<generic animal sound>'
    4. Multiple Inheritance
      Note:  Python supports multiple inheritance, where a class may have more than one superclass.  We will not cover that in 15-112.
       
  9. Old-Style vs New-Style Classes
    # old-style classes are created like this (no superclass):
    class OldStyle: pass
    
    # new-style classes include the superclass in parentheses, like this:
    class NewStyle(object): pass
    
    # new-style classes are much more "OOPy" (they have a more complete object model).
    # For example, new-style classes are types, and their instances are of that type:
    
    obj = NewStyle()
    print isinstance(obj, NewStyle) # True
    print type(obj) == NewStyle     # True
    print type(obj)                 # <class 'NewStyle'>
    
    # old-style classes do not work the same way:
    
    obj = OldStyle()
    print isinstance(obj, OldStyle) # True
    print type(obj) == OldStyle     # False!
    print type(obj)                 # <type 'instance'>
  10. Test Methods
    Q: If you write a method C.foo(), should you write a test method C.testFoo()?
    A: Yes.
    Q: But then how do you call the test method?  Do you create an instance of C on which to call that test method?
    A: No.  You make the test method an @classmethod.
    Q: Then should you also have a C.testAll() method?
    A: Yes.  That's a fine idea.  Then you can call this single method (also an @classmethod) from outside the class to run all your test methods inside the class.
     
  11. Examples from class
    1. Animation With Classes
      See AnimationWithClasses

       
    2. Previous examples
      1. Text Adventure
        See PartialAdventure.py.
      2. Fraction
        See Fractions.py.

carpe diem   -   carpe diem   -   carpe diem   -   carpe diem   -   carpe diem   -   carpe diem   -   carpe diem   -   carpe diem   -   carpe diem