Today

Implementing the class vs. Using the class

Class definition of an object type Vs. Instance of a class

class name is the type: class Coordinate(object) instance is one specific object: coord = Coordinate(1,2)
class is defined generically
  • use self to refer to some instance while defining the class.
  • self is a parameter to methods in class definition
Data attribute values vary between instances c1 = Coordinate(1,2); c2 = Coordinate(3,4)
  • c1 and c2 have different data attribute values c1.x and c2.x because they are different objects.
class defines data and methods common across all instances instance has the structure of the class

Why use OOP and Classes of Objects?

Recap: groups of objects have attributes

Recap: How to define a class

class Animal(object):
  def __init__(self, age):
    self.age = age
    self.name = None

myanimal = Animal(3)

Getter and setter methods

class Animal(object):
  def __init__(self, age):
    self.age = age
    self.name = None

  def get_age(self):
    return self.age
  def get_name(self):
    return self.name

  def set_age(self, newage):
    self.age = newage
  def set_name(self, newname==""):
    self.name = newname

  def __str__(self):
    return "animal:" + str(self.name) + ":" + str(self.age)

Recap: An instance and dot notation

Information hiding

Python not great at Information hiding

Default arguments

Hierarchies

Example:

Hierarchies

Class hierarchy example

Inheritance: parent class

class Animal(object):
  def __init__(self, age):
    self.age = age
    self.name = None
  def get_age(self):
    return self.age
  def get_name(self):
    return self.name
  def set_age(self, newage):
    self.age = newage
  def set_name(self, newname = ""):
    self.name = newname
  def __str__(self):
    return "animal:"+str(self.name)+":"+str(self.age)

Inheritance: Subclass

class Cat(Animal):  #inherits all attributes of Animal
  def speak(self):  #adds new functionality of speak method
    print("meow")
  def __str__(self):  #overrides __str__
    return "cat:"+str(self.name)+":"+str(self.age)

Which method to use?

Example: Person

class Person(Animal):  # parent class is Animal
  def __init__(self, name, age):
    Animal.__init__(self, age)  # call Animal constructor
    self.set_name(name)         # call Animal method
    self.friends = []           # add a new data attribute

  # new methods
  def get_friends(self):
    return self.friends
  def add_friend(self, fname):
    if fname not in self.friends:
      self.friends.append(fname)
  def speak(self):
    print("hello")
  def age_diff(self, other):
    diff = self.age - other.age
    print(abs(diff), "year difference")

  #override Animal's __str__ method
  def __str__(self):
    return "person:"+str(self.name)+":"+str(self.age)

Example: Student

import random  #bring in methods from random class

class Student(Person):   # inherits Person and Animal attributes
  def __init__(self, name, age, branch=None):
    Person.__init__(self, name, age)
    self.branch = branch  # adds new data
  def change_branch(self, branch):
    self.branch = branch
  def speak(self):
    r = random.random()  # From Python docs: random() method in random class gives back float in [0,1)
    if r < 0.25:
      print("I have homework")
    elif 0.25 <= r < 0.5:
      print("I need sleep")
    elif 0.5 <= r < 0.75:
      print("I should eat")
    else:
      print("I am watching YouTube")
  def __str__(self):
    return "student:"+str(self.name)+":"+str(self.age)+":"+str(self.major)

Class variables and the Rabbit subclass

Class variables are different from data attributes. Class variables and their values are shared between all instances of a Class.
class Rabbit(Animal):
  tag = 1
  def __init__(self, age, parent1=None, parent2=None):
    Animal.__init__(self, age)
    self.parent1 = parent1
    self.parent2 = parent2
    self.rid = Rabbit.tag  #self.rid is an instance attribute. This assignment accesses class variable Rabbit.tag
    Rabbit.tag += 1 #incrementing class variable changes it for all instances that may reference it
In this example, tag used to give a unique ID to each rabbit instance.

Rabbit Getter Methods

class Rabbit(Animal):
  tag = 1
  def __init__(self, age, parent1=None, parent2=None):
    Animal.__init__(self, age)
    self.parent1 = parent1
    self.parent2 = parent2
    self.rid = Rabbit.tag
    Rabbit.tag += 1
  def get_rid(self):
    return str(self.rid).zfill(3)  # zfill is a method on a string to pad the beginning with zeros, e.g., 001 not 1

  # getter methods specific for a Rabbit instance; there are also
  # get_name and get_age inherited from Animal
  def get_parent1(self):
    return self.parent1
  def get_parent2(self):
    return self.parent2

Working with your own types

def __add__(self, other):
  # returning object of same type as this class
  return Rabbit(0, self, other)  # recall Rabbit's __init__(self, age, parent1, parent2)

Special method to compare two Rabbits

Two rabbits are equal if they have the same two parents
def __eq__(self, other):
  parents_same = self.parent1.rid == other.parent1.rid and self.parent2.rid == other.parent2.rid
  parents_opposite = self.parent1.rid == other.parent2.rid and self.parent2.rid == other.parent1.rid
  return parents_same or parents_opposite

Object Oriented Programming

Summary

Key Topics

Overview of course

Thinking computationally: abstractions, algorithms, automated execution