Computer Science 15-112, Fall 2011
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. Examples from class
    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. Examples from class
    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