Weeks 12: Classes and Objects

Class Recordings

To access recordings, sign in with your @swarthmore.edu account.

Monday

Section 1 (Joshua)

Section 2 (Kevin)

Wednesday

Section 1 (Joshua)

Section 2 (Kevin)

Announcements

  • Lab 10 is available now, and it’s due before Midnight on MONDAY, 11/29, but don’t let that fool you! You should start early because…​

    • There will be no ninja sessions this week (Wed 11/24 or Fri 11/26).

    • We will have classes and labs on Wednesday 11/24 — you’re responsible for that material.

  • Quiz 5 is next Friday, 12/03.

Week 12 Topics

  • Object-Oriented Programming

  • Defining Classes

  • The implicit self parameter

  • Class methods

  • The constructor, __init__

  • String conversion, __str__

  • Access methods (getters)

  • Mutator methods (setters)

Monday

Finishing Recursion

Before moving on to new content, let’s wrap up merge sort and recursive graphics.

Objects Overview

So far, we have used lots of objects and their methods. Recall that an object consists of both data and methods — an object knows stuff and can do stuff. Here are some examples of objects we have used:

  • string objects (type str):

    • data: characters in the string

    • methods: .split(), .strip(), .isdigit(), etc.

  • list objects (type list):

    • data: elements in the list

    • methods: .append(), .sort(), etc.

  • Zelle Circle objects:

    • data: radius, center point, fill color, etc.

    • methods: .draw(), .move(), .setFill(), .getCenter(), etc.

  • Earthquake objects from labs 8 & 9

Today, consider the task of managing pizza orders. Each pizza you make has a number of slices and a topping (unless it’s plain). The pizza can be cooked or not. Things we might want to do with a pizza include adding a topping, cooking the pizza, and serving a slice (as long as the pizza has already been made).

Using what we know so far, writing a pizza program would first need to establish the data, e.g.:

pizza1 = ["plain", 8, True]  # True represents that it has been cooked
pizza2 = ["pepperoni", 6, True]
pizza3 = ["mushroom", 8, False]
pizzas = [pizza1, pizza2, pizza3]

Now, let’s say we want to order a slice:

pizza = input("Which pizza do you want? ")
for i in range(len(pizzas)):
    if pizzas[i][0] == pizza:
        if (pizzas[i][2] and (pizzas[i][1] > 0)):
            pizzas[i][1] -= 1
            print("Here is your slice of %s pizza.  There are %d slices left."
       	        % (pizza, pizzas[i][1]))

We could extend this to add data and other functionality. However, this isn’t a very satisfying solution. The code will quickly get unwieldy, becoming susceptible to bugs. It also isn’t easy to read, or reuse for a different application. We will return to the concept of object-oriented programming to come up with a better solution.

Classes and Object-Oriented Programming

In object-oriented programming, we saw that objects described complex types that contained data and methods that could act upon the data. For example, we saw that lists and strings were objects, as were elements of the Graphics library. Each of the objects we created stored data (e.g., a center, radius and color for Circle objects) and methods (e.g., move(), getCenter(), setFill()).

Creating a class defines a new type of an object. Classes define the specific methods and data an object stores. We will spend the next two weeks defining classes and creating objects from them. To start, we’ll consider a class definition to solve the problem above.

Open up pizza.py. Read through the mostly completed class definition for a pizza. Using this class, we will be able to define Pizza objects.

Defining a Class

Open up pizza.py. What do you think each line is doing? What can you explain with your neighbor?

The first key element is the class definition, which follows the format:

class ClassName(object):

ClassName is a placeholder for the actual name you want to give; class is a keyword to indicate that you are defining a new class. The additional item, (object), is an indication that the class will follow certain protocols. You should assume that this will always be there (you can put something else in there if you want to build upon an existing class, but that’s beyond our CS 21 discussion).

Methods

Within the class, you will see several class methods. Methods follow the same format as functions, but have the distinction of belonging to a specific class.

class ClassName(object):
    def __init__(self, param1, ...):
        #Constructor - initializes object and data

    def getter1(self):
        #Return some piece of information

    def setter1(self, valToSet):
        #Change something about the data

Two broad categories of methods are accessors (or getters) and mutators (or setters). Some methods do both. Methods are the primary method for interfacing with the data maintained within the class - we do not directly touch the data, but rather use methods to get/set information. This aspect of classes is critically important: Defining a class allows you to hide the details of how the class works and how it stores its underlying data. The class provides an interface (methods) for users of the class.

For example, consider lists. When we want to add a value to the end of the list, we do not directly access the data within the list. Rather, we use the append(x) method which handles that work for us.

Python also has special built-in methods that are flanked by underlines (e.g., __init__()). __init__ gets called whenever we call the constructor to create a new object. For example, when creating a Point object:

pt = Point(x, y)

Constructor

Python will run the __init__(self, x, y) method of the Point class. Every class must have a constructor — it specifies the initial data we need to keep track of and gives it a starting value. There are many other special methods that we’ll skip for right now. These include __len__ (to obtain the length of a sequence e.g., len(ls) calls the list.__len__() method) and __str__ (to convert an object to a string representation, str(x)).

Wednesday

self parameter

self is perhaps the single most confusing feature of python classes. You will see self everywhere when you are writing and designing a class. However, you will not see self at all when using a class, which is why we haven’t talked about it until this week.

Notice that all methods in a class have a self parameter as the first parameter. This is required, but users of the class do not provide this argument; instead Python automatically assigns the object that called the method as the self parameter. For example, if we create a Pizza object, pizza and call pizza.serveSlice(), the self parameter points to pizza. This is common source of confusion when writing your first class. If you look at the definition of serveSlice(self), it looks like you need to pass in one argument for the self parameter. But since python does this automatically, users actually call this method with zero arguments, e.g., pizza.serveSlice(). Calling serveSlice with an extra argument will result in a seemingly bizarre error message:

yum.serveSlice(1)
TypeError: serveSlice() takes exactly 1 argument (2 given)

Note that providing one user argument to serveSlice() reports an error that two arguments were provided. The automatic self was the other (and technically first) argument.

In summary, always put self as the first parameter of a class method when writing a method, but ignore this parameter when calling the method.

The meaning of self

You may be wondering: if self is part of every method, but it’s ignored by the user, why do we need it in the class definition? Recall that objects both know stuff and do stuff. The method definitions inside the class are how classes/objects do stuff, and the self variable is how a particular object knows stuff. In any method, we can declare a variable with a self. prefix, (e.g., self.cooked=False). If we set the value of one of these special self. attributes in one method, we can access the value in another method without passing an additional parameter. In effect, all methods share all the self. attributes.

Typically we use the __init__ method to define and initialize values for all attributes we will be using at some point in the class.

Pizza class

Test out how the Pizza class works. What is the output of each of these commands, and can you draw the stack diagram that accompanies it?

>>> from pizza import *
>>> pizza = Pizza("mushroom")
>>> print(pizza)
>>> pizza.makePizza()
>>> pizza.serveSlice()
>>>

Testing a Class

Just as with top-down design, you should practice incremental development when defining a class. Usually, this involves writing a function, thoroughly testing it, moving onto the next function, etc. There are several options for how to test, including using a separate main program. However, a common strategy is to include some test code at the bottom of your class definition file. This way, the test code can always be run in the future when updates are made. To do so, remember to include the following lines at the bottom of your file:

if __name__ == "__main__":
    # Write testing code here

The first line ensures that the testing code does not run when you import the file, but rather only when someone calls the file from the command line:

$ python3 pizza.py

Friday

No class, enjoy Thanksgiving!