Lecture 6: Classes and Objects

Eric Brauer

Outline

  • What are they? Why use objects?
  • Data attributes and Methods
  • Calling Objects
  • Objects vs. Classes
  • Functions vs. Methods

In The Beginning…

instructions
if condition
loop
more instructions
...

Basically, start on the first line and go to the end. No way to reduce complexity, to reduce redundant code, or allow the code to be reused in any way

Procedural Programming (functions)

def calculate_area(length, width):
...

# instructions
x = calculate_area(l1, w1)
y = calculate_area(l2, w2)
...

We can organize blocks of code that perform a specific task into functions. This is especially useful when we will repeat certain tasks many times. However, the function has to be defined in the same place as where it’s called.

Modular Programming (Modules)

import mycode

x = mycode.calculate_area(l, w)

Additionally, we can put our functions into modules, and import these modules into other programs. There still exists a limitation, though: data in our functions is not persistent. Every time we use calculate_area, we are sending it new values to work with.

The Next Step: Objects

Objects are different in that they contain both attributes and methods.

Consider a shape like a rectangle: it has attributes like length, width, colour.

It also can be interacted with: it can be moved, it can be changed, etc.

Put more concretely, an object can contain data and functions (methods).

In Python, Everything Is An Object

We’ve already been working with objects in this course, and you’ve probably already experienced the advantages of Object-Oriented Programming!

>>>> x = 'hello world'  # creating a string object
>>>> x
'hello world'  # string objects have an attribute: a series of characters
>>>> x.upper()
'HELLO WORLD'  # string objects come with useful methods such as .upper()

Ownership

The dot (.) is a way of indicating how things are organized. It tells us that a thing belongs to another thing.

You might see it used when working with imported modules:

import sys

args = sys.argv  # argv is a list that is found inside the sys module

Or it might be used to indicate that the thing is a method/attribute belonging to an object.

x.upper()  # upper() is a method belonging to the x string object.

We Can Create Our Own Objects Using Classes

A class is a way to define a template for one or more objects we wish to create.


class Rectangle:  # name of our class

    width = 5  # you can just define attributes inside the class
    length = 10

    def get_area(self):  # our first method
        return self.width * self.length  # just returns width times length

Creating An Object From A Class

Once you define a class, you can create many objects from that class.

>>>> r1 = Rectangle()
>>>> r2 = Rectangle()
>>>> r1.length
10
>>>> r2.length
10
>>>> r1.get_area()
50

Setting Initial Attributes Using __init__

Our Rectangle class would be more useful if we could start with different values for length and width. Fortunately, we can using a special method called __init__.

class Rectangle:

    def __init__(self, length, width):
        self.length = length
        self.width = width

    def get_area(self):
        return self.length * self.width

>>>> r1 = Rectangle(10, 5)  # when you create a new Rectangle object, 
>>>> r2 = Rectangle(25, 10) # it will first call __init__
>>>> r2.get_area()
250

Self Keyword

The self keyword has to be the first parameter of each object method, and is in front of each object attribute.

It identifies things as belonging to the object that it is created with.

Parameters in methods behave exactly like parameters in functions, they are forgotten once the method ends.

Therefore, in order for attributes to be remembered outside of __init__ is to save them into self.

r1 = Rectangle(10, 5)  # 10 is length, 5 is width. Passed into __init__. 
class Rectangle:

    def __init__(self, length, width):  # 3 parameters but 2 arguments? Don't worry about it!
        self.length = length  # self.length is an attribute, and is persistent.
        self.width = width  # without these lines, we would lose length and width.

Another Example

Consider a ‘Student’: students have names, student numbers, addresses, payment info, courses and transcripts.

Students can be enrolled in courses, can drop courses, have a GPA, make payments, graduate, etc.

  • How would you contain all of a student’s information? A dictionary?
  • How would you calculate a student’s GPA?
  • How do you separate each student’s grades and GPA from another’s?

Using Objects:

student1 = Student('joe', '3124')
student1.add_course('uli101')
student1.assign_grade('uli101', 72)
x = student1.get_gpa()
student2 = Student('mary', '3142')

Objects and Classes

In the previous example, we can see two instances of the Student class.

student1 = Student('joe', '3124')

is one of them.

  • student1 is an object, so is student2. Each object will have their own attributes.
  • Student is a class, which defines methods and attributes for each object.
  • When we initialize an object, we might pass in some preliminary data (for example, name and student id here).

Objects are mutable and persistent. Meaning: you can edit the data inside objects, and those changes will be remembered until the object is destroyed.

Inside the Student Class

class Student:  # classes get upper case letters!

    def __init__(self, name, number):
        self.name = name
        self.number = number
        self.courses = {}

    def add_course(self, course):
        self.courses[course] = None

    def assign_grade(self, course, grade):
        self.courses[course] = grade

Inside the Student Class II

class Student:  # classes get upper case letters!

    def __init__(self, name, number):  # init is used to define initial values. 
        self.name = name  # data attribute is given the value of the parameter
        self.number = number
        self.courses = {}  # we create an empty dict
  • Remember: self is a required parameter for each method inside the class.

Inside the Student Class III

    def add_course(self, course):  # again, each method needs 'self' as a parameter
        self.courses[course] = None  # adds to the dictionary. course = key

    def assign_grade(self, course, grade):
        self.courses[course] = grade  # modifies existing dict entry with new value 

Using A Class

Let’s assume that I typed those slides into a file called student_mod.py. We can import it in our Python interpreter.

>>>> from student_mod import Student
>>>> me = Student('eric', 1234)
>>>> me.name
'eric'
>>>> me.courses
{}
>>>> me.add_course('uli101')
>>>> me.courses
{'uli101', None}

Data Attributes and Methods

Data attributes are for maintaining an object’s state. As we’ve said, data attributes are persistent and mutable

Methods are functions that ‘belong’ to the object. They must have ‘self’ as a parameter

Data Attributes Can Be Created and Destroyed

me.phone_no = '416-666-9098'
del(me.phone_no)

Methods vs. Functions

>>>> type(me.addGrade)
<class 'method'>
>>>> type(Student.addGrade)
<class 'function'>
>>>> type(me.courses)
<class 'dict'>