CS 1110 - Introduction to Programming

Starting Out with Python
Chapter 10, Classes and Object-Oriented Programming, part 1

Objectives:

This lesson covers the first part of chapter 10, which discusses more ways to reuse program structures. Objectives important to this lesson:

  1. Procedural and object-oriented programming
  2. Classes
  3. Working with instances
Concepts:
Chapter 10
Before we begin, the chapter may not be clear to you. I will try to make it clearer, as always. If you need something else, try this web page, on which another practitioner gives us his thoughts on the subject.
Procedural and Object-Oriented Programming

The chapter begins, once again, with a formal definition that is not very enlightening. The author wants to compare and contrast two programming methods, procedural programming and object-oriented programming (OOP). We might argue that all programs are procedural, in that they have code sections that perform procedures: they do something. Modularized programming, as we have seen it in the previous chapters, encourages us to create a function for each separate procedure we can imagine in our programs, such as gathering input, processing the data, and displaying output. The author presents the idea that procedural programming makes a separation between data and program, but this it is not made clear at this point that object-oriented programs are different.

Moving into the object-oriented discussion, the author tells us that OOP is concerned with objects. Objects can have built-in methods, and can contain data attributes to store data. Still sounds like the functions we made that received and returned data. There is more vocabulary in this section as well.

  • encapsulation - combining data and program code in objects
  • data hiding - when an object hides the data it holds from code that is not its own code
  • method access - allowing code outside an object to access the methods of that object
  • object reusability - making it possible for other programs to use objects that we create

Having confused many readers, the text changes tactics to begin talking about an example. We are told that a software clock would have data attributes for the current second, minute, and hour. This is something we would do with variables holding values that are constantly ready to change. If this is an alarm clock, there would be one or more alarm_time attributes, and On_Off attributes for each one. The discussion continues, listing a few methods the clock would need to have: set_time, set_alarm_time, set_alarm_on, set_alarm_off. These methods would interact with the data attributes. These methods would give a user the ability to change values held by the attributes. The text tells us that the methods are necessary because the data itself is private to the clock object, and the user is not allowed to access or change the data directly. These methods are available to the user or users, making them public methods. (This is still not different from our programs. We have asked the user for choices, and the programs have used the choices.)

If we allow that the program object has private data, we should have no problem with the idea that it can have private methods, methods that are used only by the object itself. Examples would include any internal function of the clock. The text lists a few examples: increment_current_second, increment_current_minute, and sound_alarm. This completes the first section of the chapter.

Classes

A class is a template, a pattern, or a blueprint for something. It is useful to create classes for complex objects so that we can quickly make more instances of them. Think of a class as being a recipe. Suppose we have a class for apple pie. The class tells us what to do to make one, what its ingredients are, how to bake it, and what it is good for. Every time we follow that recipe and bake an apple pie, we are creating a new instance of apple pie, an actual, individual example of one, a real object. In a sense, a class is a theoretical concept. It gives us the power to make instances of that class. An instance is a real, functional object, not just an idea, that contains all the parts that the class definition describes.

Like functions, classes are created with definitions. You begin a class definition with the keyword class, instead of the word def. The example in program 10-1 defines a class called Coin. Let's examine it.

class Coin:
    def __init__(self):
        self.sideup = 'Heads'

    def toss(self):
        if random.randint(0, 1) == 0:
            self.sideup = 'Heads'
        else:
            self.sideup = 'Tails'

    def get_sideup(self):
        return self.sideup

In the code fragment above, the class called Coin is defined, and three methods are defined in it. The text points out that it is common for class names to begin with a capital letter. This makes them look different from other types of "variables". The text mentions that the three methods inside the class definition look like any other function definitions. Well, not exactly.

  • First, each of these methods expects a parameter that is called self. This is to tell the methods that they are to work with the object that they are part of. This is apparently a standard for methods in a class. The thing is, when they are eventually called they will not be passed an argument for this parameter. More on this in a minute.
  • The method with the really weird name, __init__(), is called that by another standard practice. This is a method that is supposed to be included in classes. It runs automatically. init stands for initializer. It is supposed to be the first method you create in a class. In this case, it only initializes the value of one attribute, self.sideup. The text takes a moment at the end of the discussion to mention how the method's name is spelled: two underscores before and another two after the name init. Wow, guy, think you took long enough to bring that up?
  • The toss() method simulates a coin toss, randomly assigning Heads or Tails to the sideup attribute.
  • The get_sideup() method returns the current value of the sideup attribute.

What the text does not mention is that toss() and get_sideup() are methods that are going to be called by the main() function. This code is added in program 10-2. The program creates the class called Coin, then creates an instance of this class in the main() function. The line that does this is

my_coin = Coin()

This is a little misleading. We are not calling a function called Coin. We are invoking the class called Coin, which is expecting arguments, but not really. No argument is passed to the class. It already knows to include the self argument where it is needed. Notice that the program does not actually pass any arguments to the methods of the object either, as I warned you in the bullets above. They are only expecting values for self. The self parameter is a built-in reference for the object that contains the code that is being called. In other words, toss() is a method of an object, and we want to assign its calculated value to an attribute of that same object. It's really simpler than it looks.

The text runs through this example program for a few pages before mentioning the next item: hiding attributes. Example program 10-3 demonstrates that code in a program can write to attributes in an instance of a class, if we only write the code as the text has shown it to us so far. This is bad programming, if we are at all concerned about the honesty of our coin tossing program, and about its vulnerability to hacking by a person who can inject a new line of code into it. This concern is more valid if the class definition is not actually in our program file, but this problem will be addressed in a minute. For now, the problem is that code outside the object itself can rewrite the value of an attribute. The text overcomes this problem with a simple solution. The name of the sideup attribute is changed to __sideup. This is apparently enough. When the interpreter sees an attribute with two underscores as the first characters of its name, that attribute becomes private to the methods inside the instance/object it is part of.

Around page 433, the book tells us about a better security tool: storing classes in modules. If your user sees the objects that are being created as mysterious black boxes, they are less likely to have an inspiration about circumventing your code. In example program 10-5, the author creates a file to be saved as coin.py, which will be a new importable module. This file imports random, then it defines the class Coin. It is the file we have been working on so far in the chapter. Example program 10-6 imports the coin module, creates a Coin object by referencing the class the same way you would reference a function in a module, then runs the rest of the main() function we have seen in the other recent examples.

The next example starts with the idea that we can and should create a new module to hold our new class definition. This file will hold the definition of a class called BankAccount, which will have several methods, some of which will use parameters in addition to the required self parameter. This file will be stored as bankaccount.py, which is imported by example program 10-8. You can think of this program as an interface to an ATM. It uses the techniques discussed so far in the chapter. When an object of this class is created, its __init__() method expects to be given an opening balance, which is assigned as the value of the object's __balance attribute. This is an example of passing an argument to a class when creating an instance of that class. The __balance attribute is initialized when the object is created, incremented when the deposit() method is used, decremented when the withdraw() method is used (unless the value of __balance is not sufficient), and reported to the user when the get_balance() method is used.

As it is, program 10-8 is only good for setting up a new account. In a real ATM program, we would have to check the available balance in an existing account. We could combine the two ideas to create a temporary account in the memory of the ATM which would retrieve the balance information from an account in the cloud (on our bank's network), This account would be modified by the user, then updates would be sent to the official database.

Program 10-9 adds a new method that the text recommends as a standard: the __str__() method is intended to return the "state" of a program. For the bank account program, the author defines the state as the value of the current balance. This is obtained by reading the value of self.__balance. This may seem like duplication to you, since the get_balance() method returns the same value. The difference is that the __str__() method is not called by a user. It is called automatically when you pass the name of the bank account object to a function. In example program 10-10, this object is passed to the print() function on line 19. This seems like magic, and it is, really. As the text explains, the __str__ method is called automatically, because Python looks for it, and knows to use it as the method that generates the string that represents an object. Makes perfect sense...

Working with Instances

The text opens this section with an observation that should be self-evident: each instance of a class has its own set of attributes. Well, that is as it should be: each instance is a separate object. That is what the self parameter tells a method: do the following with respect to the attributes and methods of this object. The text presents a short program that creates three instances of the Coin class. The program then calls the toss() method of each instance, then reports the state of each instance, showing that they are not all the same (well, they could be, but it's unlikely). The thing to learn from this example is the syntax to access the methods and attributes of the separate objects. The key is to keep track of which object you should be accessing.

The text begins a section about accessors and mutators on page 445 in the banana edition. These are methods that provide the ability to read and change date in an object's attributes.

  • An accessor is a method that can read and return a value from an attribute, but cannot change that value. Accessors are also called getters, because they can get values.
  • A mutator is a method that can store or change a value in an attribute. Mutators are also called setters, because they can set values. A mutator method should validate data before trying to write it to an attribute.

The text goes on at length about this concept, making sure that we understand the necessity of these methods. If the user is not given what we might call curated access to the data, running reports and making changes to data in attributes would require the help of a programmer. I have worked with systems like this in the past, and I can tell you from experience that users are never happy with them. Being able to ask for what they want when they want it, and being able to change what they are allowed to change, are empowering for an employee/user.

On page 448, the author discusses passing an object as an argument. He creates a new object:

my_coin = coin.Coin()

This code is different from the code example above because the author is now calling the Coin class from the coin library module where he stored it. I think his naming convention could be a little more verbose. In the next line, he passes the name of the new object to a function:

show_coin_status(my_coin)

The text explains that passing the object as an argument does not pass a copy of the object. What actually happens is that the function being called will receive a reference (also called a pointer) to the real object. Anything the function does will affect the actual object, not a copy.

That is enough for this lesson.


Assignments

Assignments for these chapters will be found in Blackboard. We will explore that in class.