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. Example from class

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)
      return hash(hashables) # since tuples are hashable

      # Here is another way to write your own hash function based on Bernstein's hash function:
      # For more on hash functions, google your own tutorial, such as this one.
      # def __hash__(self):

      # hashables = (self.name, self.age)
      # 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. Example from class
    See:  Polynomial.py

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