Learning Python 3 with the Linkbot/Intro to Object Oriented Programming in Python 3

From LinkbotLabs
Jump to: navigation, search

Object Oriented Programming

Up until now, the programming you have been doing has been procedural. However, a lot of programs today are Object Oriented. Knowing both types, and knowing the difference, is very important. Many important languages in computer science such as C++ and Java, often use OOP methods.
Beginners, and non-programmers often find the concept of OOP confusing, and complicated. This is normal. Don't be put off if you struggle or do not understand. There are plenty of other resources you can use to help overcome any issues you may have, if this chapter does not help you.
This chapter will be broken up into different lessons. Each lesson will explain OOP in a different way, just to make sure OOP is covered as thoroughly as possible, because IT IS VERY IMPORTANT. Before the lessons, there is an introduction which explains key concepts, terms, and other important areas of OOP, required to understand each lesson.

Introduction

Think of a procedure as a function. A function has a specific purpose. That purpose may be gathering input, performing mathematical calculations, displaying data, or manipulating data to, from, or in, a file. Typically, procedures use data which is separate from code for manipulation. This data is often passed between procedures. When a program becomes much larger and complex, this can cause problems. For example, you have designed a program which stores information about a product in variables. When a customer requests information on a product, these variables are passed to different functions for different purposes. Later on, as more data is stored on these products, you decide to store the information in a list or dictionary. In order for your program to function, you must now edit each function that accepted variables, to now accept and manipulate a list or dictionary. Imagine the time that would take for a program that was hundreds of megabytes, and hundreds of files in size! It would drive you insane! not to mention, errors in your code, are almost guaranteed, just because of the large volume of work and possibilities to make a typo or other error. This is less than optimal. Procedural programming is centered on procedures or functions. But, OOP is centered on creating Objects. Remember how a procedural program has separated data and code? Remember how that huge program was hundreds of files and would take FOREVER to edit? Well, think of an object as a sort of "combination" of those files and data into one "being". In a technical sense, an Object is an entity which contains data, AND procedures (code, functions, etc.).

Data inside an object is called a data attribute.

Functions, or procedures inside the object are called methods.


Think of data attributes as variables.

Think of methods as functions or procedures.

Let's look at a simple, everyday example. The light and light switch in your bedroom. The data attributes would be as follows.

  • light_on (True or False)
  • switch_position (Up or Down)
  • electricity_flow (True or False)

The methods would be as follows.

  • move_switch
  • change_electricity_flow

The data attributes may or may not be visible. For example, you cannot directly see the electricity flowing to the light. You only know there is electricity, because the light is on. However, you can see the position of the switch (switch_position), and you can see if the light is on or off (light_on). Some methods are private. This means that you cannot directly change them. For example, unless you cut the wires in your light fixture (please don't do that, and for the sake of this example, assume that you don't know the wires exist), you cannot change the flow of electricity directly. You also cannot directly change if the light is on or off (and no, you can't unscrew the bulb! work with me here!). However, you can indirectly change these attributes by using the methods in the object. If you don't pay your bill, the change_electricity_flow method will change the value of the electricity_flow attribute to FALSE. If you flip the switch, the move_switch method changes the value of the light_on attribute.

By now you're probably thinking, "What does this have to do with Python?" or, "I understand, but how do I code an Object?" Well, we are almost to that point! One more concept must be explained before we can dive into code.

In Python, an object's data attributes and methods are specified by a class. Think of a class as a blueprint to an object. For example, your home - the object that you live in - you can also call it your pad, bungalow, crib, or whatever, was built based on a set of blueprints; these blueprints would be considered the class used to design your home, pad, crib, ahem, you get the idea.

Again, a class tells us how to make an object. In technical terms, and this is important here, a class defines the data attributes and methods inside an object.

To create a class, we code a class definition. A class definition is a group of statements which define an object's data attributes and methods.

Lesson One

Below is a Procedural program that performs simple math on a single number, entered by a user.

# Program by Mitchell Aikens
# No Copyright
# 2012
 
# Procedure 1
def main():
    try:
        # Get a number to maniuplate
        num = float(input("Please enter a number to manipulate.\n"))
        # Store the result of the value, after it has been manipulated
        # by Procedure 2
        addednum = addfive(num)
        # Store the result of the value, after it has been manipulated
        # by Procedure 3
        multipliednum = multiply(addednum)
        # Send the value to Procedure 4
        display(multipliednum)
    # Deal with exceptions from non-numeric user entry
    except ValueError:
        print("You must enter a valid number.\n")
        # Reset the value of num, to clear non-numeric data.
        num = 0
        # Call main, again.
        main()
 
# Procedure 2
def addfive(num):
    return num + 5
 
# Procedure 3
def multiply(addednum):
    return addednum * 2.452
 
# Procedure 4
def display(multi):
    # Display the final value
    print("The final value is ",multi)
 
# Call Procedure 1
main()

If we were to enter a value of "5", the output would be as shown below.

Please enter a number to manipulate.
5
The final value is  24.52

If we were to enter a value of "g", and then correct the input and enter a value of "8", the output would be as shown below.

Please enter a number to manipulate.
g
You must enter a valid number.

Please enter a number to manipulate.
8
The final value is  31.875999999999998

Below, is a Class, and a program which uses that class. This Object Oriented Program does the same thing as the procedural program above. Let's cover some important OOP coding concepts before we dive into the Class and program. To create a class, we use the class keyword. After the keyword, you type the name you want to name your class. It is common practice that the name of your class is capitalized. If I wanted to create a class named dirtysocks, the code would be:

class Dirtysocks

The Class is shown first. The program which uses the class is second.

# Filename: oopexample.py
# Mitchell Aikens
# No Copyright
# 2012
# OOP Demonstration - Class
 
class Numchange:
 
    def __init__(self):
        self.__number = 0
 
    def addfive(self,num):
        self.__number = num
        return self.__number + 5
 
    def multiply(self,added):
        self.__added = added
        return self.__added * 2.452

The program which uses the class above, is below.

# Filename: oopexampleprog.py
# Mitchell Aikens
# No Copyright
# 2012
# OOP Demonstration - Program
 
import oopexample
 
maths = oopexample.Numchange()
 
def main():
 
    num = float(input("Please enter a number.\n"))
 
    added = maths.addfive(num)
 
    multip = maths.multiply(added)
 
    print("The manipulated value is ",multip)
 
main()

After looking at that program, you are probably a bit lost. That's OK. Lets start off by dissecting the class. The class is named "Numchange" There are three methods to this class:

  • __init__
  • addfive
  • multiply

These three methods each have a similar code.

def __init__(self):
def addfive(self,num):
def multiply(self,added):

Notice how each method has a parameter named "self". this parameter must be present in each method of the class. This parameter doesn't HAVE TO be called "self", but it is standard practice, which means you should probably stick with it. This parameter is required in each method because when a method executes, it has to know which object's attributes to operate on. Even though there is only one Object, we still need to make sure the interpreter knows that we want to use the data attributes in that class. So we play it safe...and use the "self" parameter.

Lets look at the first method.

def __init__(self):

Most Classes in python have an __init__ which executes automatically when an instance of a class is created in memory.(When we reference a class, an instance (or object)of that class is created). This method is commonly referred to as the initializer method. When the method executes, the "self" parameter is automatically assigned to the object. This method is called the initializer method because is "initializes" the data attributes. Under the __init__ method, we set the value of the number method to 0 initially. We reference the object attribute using dot notation.

def __init__(self):
    self.__number = 0

The self.__number = 0 line simply means ""the value of the attribute "number", in the object, is 0"".

Let's look at the next method.

def addfive(self,num):
    self.__number = num
    return self.__number + 5

This method is named "addfive. It accepts a parameter called "num", from the program using the class. The method then assigns the value of that parameter to the "number" attribute inside the object. The method then returns the value of "number", with 5 added to it, to the statement which called the method.

Let's look at the third method.

def multiply(self,added):
    self.__added = added
    return self.__added * 2.453

This method is named "multiply". It accepts a parameter named "added". It assigns the value of the parameter to the "added" attribute, and returns the value of the "added" attribute multiplied by 2.452, to the statement which called the method.

Notice how the name of each method begins with two underscores? Let's explain that. Earlier we mentioned that an object operates on data attributes inside itself using methods. Ideally, these data attributes should be able to be manipulated ONLY BY METHODS IN THE OBJECT. It is possible to have outside code manipulate data attributes. To "hide" attributes, so only methods in the object can manipulate them, you use two underscores before the object name, as we have been demonstrating. Omitting those two underscores in the attribute name, allows for the possibility of manipulation from code outside the object.

Lets look at the program which uses the class we just dissected.

Notice the first line of non comment code.

import oopexample

This line of code imports the class, which we have saved in a separate file (module). Classes do not have to be in a separate file, but it is almost always the case, and thus is good practice to get used to importing the module now.

The next line:

maths = oopexample.Numchange()

This line creates an instance of the Numchange class, stored in the module named "oopexample", and stores the instance in the variable named "maths". The syntax is: modulename.Classname() Next we define the main function. Then, we get a number from the user.

The next line added = maths.addfive(num) sends the value of the "num" variable to the method named "addfive", which is part of the class we stored an instance of in the variable named "maths", and stores the returned value in the variable named "added".

The next line multip = maths.multiply(added) sends the value of the variable "added", to the method named "multiply", which is part of the class we stored an instance of in the variable named "maths", and stores the returned value in the variable named "multip".

The next line prints "The manipulated value is <value of multip>". The last line calls the main function which executes the steps outlined above.

Object Oriented Inheritance: Extending the Linkbot Class

Throughout all of our code examples, you've been seeing these lines of code over and over again:

...
import linkbot
myLinkbot = linkbot.Linkbot('ABCD')
...
myLinkbot.move() # etc etc

If you guessed that the myLinkbot variable is actually a Python object, you are correct! The Linkbot() function returns an object of the Linkbot class, which is defined inside of the linkbot package. An extremely powerful and important concept that object-oriented languages use is a concept called "inheritance". Sometimes, a programmer may want to create a new object that is very similar to or related to objects that have already been written.

For instance, the Linkbot class contains a variety of functions that move the motors, beeps the buzzer, etc. However, it would be nice if I could write my own object for controlling a Linkbot-I that has 2 wheels and a caster attached. I could call the new object "LinkbotCar", and it could have member functions that make it drive forward, drive backward, turn, etc. At the same time, I still want the LinkbotCar class to do everything that the original Linkbot class did, such as changing LED colors and beeping the buzzer. Furthermore, it would be nice if the programmer didn't have to re-implement all of those methods that already exist inside the Linkbot class.

The solution to this situation is to write our LinkbotCar class such that it "extends", "inherits from", or "derives from" (all of these terms are more-or-less interchangeable) the original Linkbot class. Lets start by a quick example. Here is a program that creates a new LinkbotCar class based on the original Linkbot class, makes a new function called driveForwardDistance which drives the wheeled robot forward a certain distance, and then beeps the buzzer for half a second.

# File: 01_linkbot_car.py
 
import linkbot
import math  # for math.pi
import time  # for time.sleep()
 
class LinkbotCar(linkbot.Linkbot):           # 1
    def driveForwardDistance(self, inches):  # 2
        wheelRadius = 3.5/2.0
        degrees = (360.0/(2.0*math.pi*wheelRadius)) * inches
        self.move(degrees, 0, -degrees)      # 3
 
myLinkbotCar = LinkbotCar('ABCD')            # 4
 
myLinkbotCar.driveForwardDistance(5)         # 5
myLinkbotCar.setBuzzerFrequency(440)         # 6
time.sleep(0.5)
myLinkbotCar.setBuzzerFrequency(0)
  1. This line begins the process of making a new class called "LinkbotCar" based on the existing class "linkbot.Linkbot".
  2. Here, we define a new function called "driveForwardDistance" for the LinkbotCar class. The first argument for member functions represents the object itself. Although you can name this first variable whatever you want, all Python programmers use the name "self" by convention. The second argument is the number of inches we want the robot to travel.
  3. This is the line that actually makes the Linkbot move. Notice that we use the move() function on the self variable here.
  4. Here, we gain control of the Linkbot through the dongle. Notice that the format of this function call is slightly different from what we've seen before. Not only is the requested Linkbot ID 'ABCD' specified, we also specify that we want a "LinkbotCar" object instead of a vanilla "Linkbot" object. Vanilla "Linkbot" objects do not have the new driveForwardDistance member function that we just defined.
  5. At this line, we actually use our new function that we defined above. This makes the robot represented by the variable myLinkbotCar drive forward 5 inches.
  6. Next, we make the Linkbot beep its buzzer. Notice that setBuzzerFrequency() was originally a function inside the barobo.Linkbot class. Because our object inherits from the original barobo.Linkbot class, we can also call any member function that is in the barobo.Linkbot class.

While this style of programming may seem cumbersome at first, there are many reasons to motivate this type of organization. For instance,

  • Why don't we just hard-code all of the Linkbot movements with move() and moveJoint() functions? This might be feasible for smaller programs, but imagine if you had to write a program where a wheeled Linkbot had to travel through a complex maze. If we need the Linkbot to accurately turn and travel more than 10 times, it is probably worth the time to write a function or class to make it easier on ourselves. In the long run, we would probably save time by writing the derived class.
  • Why is writing a class better than writing helper functions? One of the main benefits of writing a class is that someone else (or you) can inherit from your new class too. Lets say for instance you wrote a LinkbotCar class that moves the Linkbot like a car and your buddy wrote a class called LinkbotMelody that could make the Linkbot play complex songs using the buzzer. You could take the two classes and inherit from both of them to create a new class that can drive the Linkbot like a car and play melodies!

Exercises

Continue extending the LinkbotCar class by adding functions turnLeft() and turnRight().

Solution
class LinkbotCar(linkbot.Linkbot):
    def driveForwardDistance(self, inches):
        wheelRadius = 3.5/2.0
        degrees = (360.0/(2.0*math.pi*wheelRadius)) * inches
        self.move(degrees, 0, -degrees)
 
    def turnLeft(self):
        self.move(-30, 0, -30)
 
    def turnRight(self):
        self.move(30, 0, 30)


Learning Python 3 with the Linkbot
 ← Recursion Intro to Object Oriented Programming in Python 3 Intro to Imported Libraries and other Functions →