CMU 15-112: Fundamentals of Programming and Computer Science
Class Notes: Object-Oriented Programming (OOP), Part 2
Writing Classes and Methods


  1. Writing Classes
  2. Writing Constructors
  3. Writing Methods
  4. Advantages of Classes and Methods

  1. Writing Classes
    # Create our own class: class Dog(object): # a class must have a body, even if it does nothing, so we will # use 'pass' for now... pass # Create instances of our class: d1 = Dog() d2 = Dog() # Verify the type of these instances: print(type(d1)) # Dog (actually, class '__main__.Dog') print(isinstance(d2, Dog)) # True # Set and get properties (aka 'fields' or 'attributes') of these instances: d1.name = 'Dot' d1.age = 4 d2.name = 'Elf' d2.age = 3 print(d1.name, d1.age) # Dot 4 print(d2.name, d2.age) # Elf 3

  2. Writing Constructors
    • Constructors let us pre-load our new instances with properties.
    • This lets us write code like so:
      d1 = Dog('fred', 4) # now d1 is a Dog instance with name 'fred' and age 4
      
    • We would like to write our constructor like this:
      def constructor(dog, name, age):
          # pre-load the dog instance with the given name and age:
          dog.name = name
          dog.age = age
      
    • Unfortunately, Python does not use 'constructor' as the constructor name. Instead, it uses '__init__' (sorry about that), like so:
      def __init__(dog, name, age):
          # pre-load the dog instance with the given name and age:
          dog.name = name
          dog.age = age
      
    • Also, unfortunately, while we could name the instance 'dog' like we did, standard convention requires that we name it 'self' (sorry again), like so:
      def __init__(self, name, age):
          # pre-load the dog instance with the given name and age:
          self.name = name
          self.age = age
      
    • Finally, we place this method inside the class and we have a constructor that we can use, like so:
      class Dog(object): def __init__(self, name, age): # pre-load the dog instance with the given name and age: self.name = name self.age = age # Create instances of our class, using our new constructor d1 = Dog('Dot', 4) d2 = Dog('Elf', 3) print(d1.name, d1.age) # Dot 4 print(d2.name, d2.age) # Elf 3

  3. Writing Methods
    • Start with a function:
      class Dog(object): def __init__(self, name, age): self.name = name self.age = age # Here is a function we will turn into a method: def sayHi(dog): print(f'Hi, my name is {dog.name} and I am {dog.age} years old!') d1 = Dog('Dot', 4) d2 = Dog('Elf', 3) sayHi(d1) # Hi, my name is Dot and I am 4 years old! sayHi(d2) # Hi, my name is Elf and I am 3 years old!

    • Turn the function into a method, and the function call into a method call, like this:
      class Dog(object): def __init__(self, name, age): self.name = name self.age = age # Now it is a method (simply by indenting it inside the class!) def sayHi(dog): print(f'Hi, my name is {dog.name} and I am {dog.age} years old!') d1 = Dog('Dot', 4) d2 = Dog('Elf', 3) # Notice how we change the function calls into method calls: d1.sayHi() # Hi, my name is Dot and I am 4 years old! d2.sayHi() # Hi, my name is Elf and I am 3 years old!

    • Finally, use self, as convention requires:
      class Dog(object): def __init__(self, name, age): self.name = name self.age = age # Now we are using self, as convention requires: def sayHi(self): print(f'Hi, my name is {self.name} and I am {self.age} years old!') d1 = Dog('Dot', 4) d2 = Dog('Elf', 3) # Notice how we change the function calls into method calls: d1.sayHi() # Hi, my name is Dot and I am 4 years old! d2.sayHi() # Hi, my name is Elf and I am 3 years old!

    • Methods can take additional parameters, like so:
      class Dog(object): def __init__(self, name, age): self.name = name self.age = age # This method takes a second parameter -- times def bark(self, times): print(f'{self.name} says: {"woof!" * times}') d = Dog('Dot', 4) d.bark(1) # Dot says: woof! d.bark(4) # Dot says: woof!woof!woof!woof!

    • Methods can also set properties, like so:
      class Dog(object): def __init__(self, name, age): self.name = name self.age = age self.woofCount = 0 # we initialize the property in the constructor! def bark(self, times): # Then we can set and get the property in this method self.woofCount += times print(f'{self.name} says: {"woof!" * times} ({self.woofCount} woofs!)') d = Dog('Dot', 4) d.bark(1) # Dot says: woof! d.bark(4) # Dot says: woof!woof!woof!woof!

  4. Advantages of Classes and Methods
    • Encapsulation
      • Organizes code
        A class includes the data and methods for that class.
      • Promotes intuitive design
        Well-designed classes should be intuitive, so the data and methods in the class match commonsense expectations.
      • Restricts access
        • len is a function, so we can call len(True) (which crashes)
        • upper is a method on strings but not booleans, so we cannot even call True.upper()
    • Polymorphism
      Polymorphism: the same method name can run different code based on type, like so:
      class Dog(object): def speak(self): print('woof!') class Cat(object): def speak(self): print('meow!') for animal in [ Dog(), Cat() ]: animal.speak() # same method name, but one woofs and one meows!