Lab 12 - Objects

Objects were introduced in lecture this week. You may also want to review the first few sections of Chapter 14 of the text (Classes and Objects - the Basics).

Recall the CashRegister example we did in class. Our goal was to simulate or model the behavior of a real cash register. In its simplest form, we just need three operations:

We decided to call these methods add_item, get_total, and clear. We then wrote a simple test case, to think about sample usage of the three methods:

cr = CashRegister()
cr.add_item(2)
cr.add_item(3)
print(cr.get_total())
print("Expected 5.0")
cr.clear()
print(cr.get_total())
print("Expected 0")

The main question is this: in order for the method get_total() to work correctly, it is going to have to “know” what the total cost of all the items is. Somehow, hidden inside the object, there needs to be a variable representing the current balance in the cash register. This variable represents the “state” of the cash register at any given moment. Variables like this are defined in a special function called an initializer or constructor which always has the name __init__ (double underscore before and after). When we write the line

cr = CashRegister()

the Python interpreter constructs a new CashRegister object and automatically calls the initializer, which creates and initializes the variables that represent the object’s attributes or “state”. We say that the resulting object is an instance of the class CashRegister.

Step through the example using CodeLens and you can see how it works.

Instance variables

When you run the sample code using codelens, pay particular attention when we create the second CashRegister. Each instance of CashRegister has its own copy of the balance variable. The variable balance that you see in the class definition above is called an instance variable, because there is a separate copy for each instance.

The purpose of the initializer is to create and initialize the instance variables. Whenever we refer to an instance variable in the code, we have to prefix it with self.

Adding arguments to the initializer

What if we want to add sales tax to the total? The tax rate differs in different places, so the tax rate should also be an attribute of the CashRegister, just like the balance. We would like to be able to specify the tax rate when we construct the CashRegister object. The way to do this is to add an additional parameter to the initializer, and use it to initialize a second instance variable:

def __init__(self, rate):
    self.balance = 0
    self.tax_rate = rate

Then, when we construct a CashRegister, we have to supply an argument for the tax rate. For example, this would construct a cash register using a 5% tax rate:

cr = CashRegister(.05)

Step through the improved example in codelens and look at the difference.

Summary

Here is a summary of what we have seen so far:

  • An object is a software model of a thing that has attributes, operations, and an identity.
  • We have seen several types of objects in Python already, including turtles, lists, and strings.
  • The operations on an object are special functions called methods and you call them on a particular object using dot notation, as in s.upper() for a string s.
  • The definition for a type of object is a class. A class isn’t an object, but it is like the recipe for making an object.
  • You can construct an instance of a class using the class name.
  • A method is like a function, but its definition is part of a class (indented beneath the class declaration).
  • When you define a method in a class, the first parameter is always self, a reference to the object on which the method is being called.
  • The attributes or “state” of an object are represented by instance variables.
  • Every class has a special method named __init__ whose job is to define the instance variables.
  • Whenever you refer to an instance variable within a class definition, you must precede with self.

Checkpoint 1

Modify the code for CashRegister by adding a new method called add_item_nontaxable(cost). The behavior should be that when we call get_total(), tax is only added for the total cost of the taxable items. For example, here is a test case:

open the file
CashRegister c = CashRegister()
c.add_item(5)
c.add_item_nontaxable(10)
print(c.get_total())
print("Expected 15.5")

Hint: Make a second instance variable to keep track of the total cost for nontaxable items.

You can find the existing code here:

<http://web.cs.iastate.edu/~smkautz/cs127f16/examples/week14/cr_class_with_tax.py>

Checkpoint 2

This is a pencil and paper exercise.

Write a short paragraph describing an interaction with a drink vending machine. To simplify things let’s assume 1) it takes only coins, and 2) there is only one kind of drink to select. Describe several scenarios - maybe you press the button before you’ve inserted enough money? Maybe it is out of drinks?

Then, come up with a description of the following:

  • What operations would be needed to simulate a vending machine? Give each one a name, return type, and description.
  • What attributes (instance variables) does a vending machine need to have, in order to implement those operations?

Explain what you have to the TA.

Checkpoint 3

_static/piggy1.jpeg

A piggy bank is a container for coins. It is usually ceramic or glass. At any given time, the coins in it have some total value. It is also possible to smash the piggy bank, after which time it contains no coins, and trying to add more coins does nothing. We can model a piggy bank in Python by defining a class PiggyBank with the following operations:

  • add_coin(value) - adds one coin having the specified value, but if the piggy bank has been smashed, this method does nothing
  • get_num_coins() - returns the number of coins currently in this piggy bank
  • get_value() - returns the total value of all the coins in this piggy bank
  • smash() - smashes this piggy bank; after being smashed, there are no coins in it and no coins can be added

Assume you have a module piggy containing the definition for a class PiggyBank with the methods above. Write some test cases that will try out all the methods. Print out the actual results that you get, along with the results you *expect* to get if it is working correctly.

Checkpoint 4

Write an implementation of the class PiggyBank described above. Make sure all your tests have the correct results!