Chapter 4
- Chapter Sections
- Section 4.1: Built-In Classes (problems)
- Section 4.2: Custom Classes (problems)
- Section 4.3: Custom Methods (problems)
- Section 4.4: Other Objects as Member Variables (problems)
- Section 4.5: Inheritance (problems)
Section 4.1: Built-In Classes
Introduction
In the Python programming language, different data types are called classes. In Python, classes include primitive types such as int, float, and bool or more complex types such as str. In the next section we will create our own classes that will be composites of other classes. In this section we will learn about class methods. Methods are a kind of function that are always associated with particular class.
Calling Methods
It makes sense to associate certain functions with some classes, but not others. For example, it makes sense to capitalize a str, but not to capitalize an int. For this reason classes have methods that only work with that class or related classes. To use a method we use the dot operator. For both methods and functions, when we use either this is referred to as a call, either a "method call" or "function call".
String Methods
A Few String Methods
Code | Description |
.capitalize() | Returns a new string that is almost identical, but the first letter of the word is a capital letter. |
.isdigit() | Returns a True or False value depending on whether the string only contains digits with no letters or other characters. |
.replace(old_part, new_part) | Returns a new string where a part of the original string has been replaced by the new part. |
The .capitalize() Method
name = "fred" @
name_cap = name.capitalize()
name_cap is equal to: "Fred"
The .isdigit() Method
num_str = "2342" @
name = "Mary"
num_str.isdigit() is equal to: True
name.isdigit() is equal to: False
The .replace() Method
generic_sentence = "Jack is from PLACE." @
sentence = generic_sentence.replace("PLACE", "New Mexico")
sentence is equal to: "Jack is from New Mexico."
Number Methods
The float type has a method called .is_integer() that takes no argument and returns True if the floating point number is really an integer, and False otherwise. The int type has a method called .bit_length() that takes no argument and returns the number of bits (1's or 0's) that are necessary to represent the integer.
The .is_integer() Method
num1 = 45.0 @
num2 = 45.0001
num1.is_integer() is equal to: True
num2.is_integer() is equal to: False
The .bit_length() Method
num = -4324 @
bits = num.bit_length()
bits is equal to 13 because this is how many binary digits it will take to represent this number.
Avoid Creating Errors
One way to accidentally generate an error is to try to call a method with the wrong class. Although different classes may have methods with the same name, they do not always. For example int does not have a method called .is_integer(). We already know an int must be an integer. If you create a variable n = 431, trying to execute n.is_integer() will create an error.
Section 4.1 Name:____________________
Built-In Classes
Score:      /5
Problems
- Complete the code below to capitalize "fred"
lower_case = "fred" @
upper_case =
- Complete the code below to capitalize "st. Louis"
lower_case = "st. Louis" @
upper_case =
- What will var be equal to?
s1 = "4932" @
var = s1.isdigit()
- What will var be equal to?
s2 = "eighty two" @
var = s2.isdigit()
- Complete the code below to create a new string that replaces "NAME" by "John". USE CODE to do this, don't just type out the sentence.
sentence = "Hello NAME, it is nice to meet you." @
new_sentence =
- Complete the code below to create a new string that replaces "PLACE" by "Detroit". USE CODE to do this, don't just type out the sentence.
sentence = "Did you know that Jack is from PLACE?" @
new_sentence =
- What will var be equal to?
num = 23.421 @
var = num.is_integer()
- What will var be equal to?
num = 42.0 @
var = num.is_integer()
Section 4.2: Custom Classes
Introduction
In this section we will learn how to create our own classes. Typically, the classes that most programmers create are composites of primitive types or other composite classes. Data types, such as int, float, and bool are primitive types that are handled by their own circuitry in the CPU and GPU of a computer. All commonly used programming languages have some way of representing these primitive types. All other types (classes) contain these primitive types, in some cases many primitive types.
Objects and Member Variables
Classes are intended to represent a type of something useful. Perhaps we want to store information about several people, such as their names and ages. To do this we can create a Person class. Then we can create what are called objects.
An object is an instance of a class. An instance is a single example of a general type. For example, the integers, 1, 4, 82, -103, are all instances of the int class. So after we create a Person class, we can create objects of the Person class to represent the individual people. We only need one class to create as many objects as we want. Think of the class as the blueprints for the object. The code for the class determines how the objects should work.
At first much of what we are required to do to create a class may be hard to understand or even seem arbitrary. As we learn more facts about classes and objects the details should make more sense.
Very Simple Person Class
class Person: @@
~def __init__( self, name, age ) @
~~self.name = name @
~~self.age = age
Creating Objects Of The Person Class
per1 = Person( "Fred", 17 ) @
per2 = Person( "Mary", 16 ) @
per3 = Person( "Mr. Smith", 97 )
The code in example (6) creates a Person class using the class keyword along with some additional code. The code in example (7) creates 3 Person objects with variable names per1, per2, and per3.
Every object has member variables associated with that object. The best way to add member variables to the objects of a class is to create what is called an __init__ method. Note that there are two underscores before and another two after the letters init. Init is an abbreviation of initialization. This method creates and initializes the member variables.
The self argument of the __init__ method is very important. The self argument is always the object calling (or using) the method. When the method is called, the self argument is the object calling the method. In example (7), when the line per1 = Person( "Fred", 17 ) runs, the __init__ method is called and while the method executes per1 is self.
Notice in example (7) two arguments are used, not the three you see in the code for the __init__ method in example (6). One argument is for the name and the other argument is for the age of the person. The reason that self is not one of the arguments that is used in example (7), is that self is the object being created.
The Dot Operator
Like with modules, we can access member variables with the dot operator. Suppose we want to change the age of per1 in example (7) to be 18 instead of 17. Suppose also per1 has recently changed his name from "Fred" to "Freddy". We can change both member variables by accessing them individually with the dot operator.
Using The Dot Operator
per1.age = 18 @
per1.name = "Freddy"
Useful Organization of Code
One reason classes are so commonly used in Python and most other programming languages, is that classes offer a way to organize code into intuitive chunks of meaningful code. They also hide complexity making code easier to understand. Classes are not actually necessary to solve any programming problem. Everything that can be done with object-oriented programming (programming using objects and classes), can be done in other ways without ever using code for classes. Classes are used simply because they make code much easier to understand and much more intuitive.
Section 4.2 Name:____________________
Custom Classes
Score:      /5
Problems
- Vocabulary Matching: Match each term with the best fitting description.
call | _____ A type of something. |
initialization | _____ A single execution of a method or function. |
class | _____ An example or instance of something. Not a type. |
object | _____ The process of giving beginning values. |
the dot operator | _____ Contain values and are part of an object. |
member variables | _____ When used with objects allows access to member variables. |
- The code below is for a class. In the box below create an two objects of this class. The first car should be a 2017 Stingray, and the second car should be a 1908 Model T.
Class Code
class Car: @
~def __init__( self, model, year ):@@
~~self.model = model @
~~self.year = year
- Create a class.
- Think up a class with two variables. Write the code for the class with an __init__ method for the two variables.
- Create two objects of the class you created above each with it's own variable values.
- Write the code for a class that:
- Represents a product that a company is selling with a name, price, and quantity sold, with an __init__ method.
- Create two objects of this class with their own variable values.
Section 4.3: Custom Methods
Introduction
So far we have seen the __init__ method that is used to initialize objects. This section covers how to make other methods. It is important to know that a method is really just a function that is designed to work specifically with objects of a particular class. For this reason we use the def keyword when we are creating methods, as we will see below and like we saw with the __init__ method.
A method's code is mostly the same as an ordinary function. One exception is that for a method the def keyword is contained inside the class and must be indented. All methods also must always take self as the first argument. The variable self is actually the object that the method is being used with. Its the object itself.
A More Advanced Person Class
class Person: @@
~def __init__(self, name, age, favorite_food): @
~~self.name = name @
~~self.age = age @
~~self.favorite_food = favorite_food @@
~def greet(self, other): @
~~print("Hello, " + other.name + ".") @
~~print("It is nice to meet you.") @
~~print("My name is " + self.name + ".") @@@
p1 = Person("Suzie", 16, "pizza") @
p2 = Person("Frank", 15, "bread") @
p2.greet(p1)
Screen
Hello, Suzie. @
It is nice to meet you. @
My name is Frank.
Other Special Methods
There are other special methods that can be used. These methods are not nearly as important as the __init__ method, but can be convenient to add to a class.
For example, we can use special methods so that we can use mathematical operations with objects of our own classes. Below is an example of a fraction class. In Python, fractions are not a built-in part of the language. So, to make our lives easier, we may decide to create a fraction class containing useful code for working with fractions, such as code that simplifies fractions.
A Fraction Class
class Fraction: @@
~def __init__(self, numerator, denominator): @
~~self.numerator = numerator @
~~self.denominator = denominator @@
~def simplify(self): @
~~f = 2 @
~~while f <= self.denominator: @
~~~if self.numerator%f == 0 and self.denominator%f == 0: @
~~~~self.numerator = self.numerator / f @
~~~~self.denominator = self.denominator / f @
~~~f += 1 @@
~def gcd(self): @
~~f = 2 @
~~greatest = 1 @
~~while f <= self.denominator: @
~~~if self.numerator%f == 0 and self.denominator%f == 0: @
~~~~greatest = f @
~~~f += 1 @
~~return(greatest) @@
~def __add__(self,other): @
~~n = self.n * other.d + other.n * self.d @
~~d = self.d*other.d @
~~frac = Fraction(n, d) @
~~return( frac ) @@
~def __str__(self): @
~~return( str(self.n) + "/" + str(self.d) ) @@
In Another File
a = Fraction(3,5) @
b = Fraction(2,3) @
c = a + b @
print(a) @
print(b) @
print(c)
Screen
3/5 @
2/3 @
19/15
Note that the fraction class above also has the special methods __str__. The __str__ method determines what will be displayed when the object is printed.
Section 4.3 Name:____________________
Custom Methods
Score:      /5
Problems
- Rewrite the Product class so that:
- The __init__ method that initializes the name, price, and quantity sold.
- Create a method that calculates the revenue, revenue = price * quantity sold.
- Create an object.
- Calculate the revenue for the object and store it in a variable called r.
Class Code (parts a and b)
Object Code (parts c and d)
- Write the code for a video game character class that:
- Uses an __init__ method to initializes the name, health, and damage.
- Create a method that prints the health and damage of the character.
- Create an object and call the method.
Class Code (parts a and b)
Object Code (part c)
- Write the code for your own class with at least 2 variables that:
- Uses an __init__ method to initializes it's two variables.
- Create a method that uses the variables.
- Write the code to create an object and use the method.
Class Code (parts a and b)
Object Code (part c)
Section 4.4: Other Objects as Member Variables
Introduction
So far we have used primitive data types, such as ints and floats, along with strings as member variables of our classes. However, member variables are not restricted to primitive types or strings. We can also use objects of other classes as member variables.
Person With Food Class
class Food: @@
~def __init__(self, name, calories): @
~~self.name = name @
~~self.calories = calories @@@
class Person: @@
~def __init__(self, name, age, favorite_food): @
~~self.name = name @
~~self.age = age @
~~self.favorite_food = favorite_food @@@
fd1 = Food("pizza", 1000) @
p1 = Person("Suzie", 16, fd1)
Dot Operator Chains
One consequence of member variables being objects themselves is that multiple dot operators can be used in a single line of code. For example, consider the Person object created in the example above.
Dot Operator Chain
fd1 = Food("pizza", 1000) @
p1 = Person("Suzie", 16, fd1) @@
print( p1.favorite_food.name ) @
print( p1.favorite_food.calories )
Screen
pizza @
1000
Within the object stored in the variable p1 is the member variable called favorite_food which is the object for the person's favorite food. Within this object are the member variables for the name and also for the calories of the food. We can access one after the other with a chain of dot operators.
There is no pratical limit to the number of links in the chain. However, long chains of operators can make code much more complicated to understand. Consider the best friend example.
Best Friends
class Person: @@
~def __init__(self, name): @
~~self.name = name @@
~def set_best_friend(self, best_friend): @
~~self.best_friend = best_friend @@@
a = Person("Mary") @
b = Person("Susannah") @
c = Person("Leah") @
a.set_best_friend(b) @
b.set_best_friend(c) @
c.set_best_friend(a) @@
print(a.best_friend.best_friend.best_friend.best_friend.name) @
Screen
Susannah
There is no limit for how long a chain like the one above must be because each best friend has their own best friend. So a chain like the one above can potentially go on forever.
Object References
Throughout the course we have used variables to "store" the values of primitive types and objects. Although we often speak this way, a Python variable does not technically store the value of the object, rather it is a reference to the object.
Behind the scenes, an object is really stored as data in a chunk of bytes in memory (RAM). A variable is the location in memory (RAM) for this chunk of bytes. Technically, a variable is an index (nonnegative integer) for the first byte of the chunk of bytes in RAM used to store the data that makes up the object. Essentially, a variable is used by the computer to find the bytes of data so it can look up the values of the ints, floats, strings, etc. stored in that chunk of bytes that makes up the object. Although with Python we rarely need to be concerned about these details, with other programming languages such as c++, understanding these details becomes more important. For us, we mainly need to understand that variables are references. The main consequence is that multiple variables can be references to the same object.
Multiple References, One Object
class Car: @
~def __init__(self, name, year): @
~~self.name = name @
~~self.year = year @@@
a = Car("Jeep",2009) @
b = a @
c = b @
d = c @
d.year = 2018 @
print(a.year)
Screen
2018
In the above code, only one object is created. However, there are four references to this one object: a, b, c, d. If we change d.year to be 2018 from 2009, then we also change a.year, b.year, and c.year to be 2018 because they are all really the same object. The references, a, b, c, d all give us access to this one object.
Object Ownership
Because different variables can reference the same object, knowing which variable is "responsible" for the object can become an issue. If you are careful in the way you write your code, this should not be too much of a problem. However, if you are less careful, or if another person is using your code, it is possible to change an object referenced by one variable and forget that you are also changing the value of other variables that reference that object. For example,
Best Friends Again
class Person: @@
~def __init__(self, name): @
~~self.name = name @@
~def set_best_friend(self, best_friend): @
~~self.best_friend = best_friend @@
a = Person("Mary") @
b = Person("Susannah") @
c = Person("Leah") @
a.set_best_friend(b) @
b.set_best_friend(c) @
c.set_best_friend(a) @@@
a.best_friend.name = "Leah" @@@
print(a.name) @
print(b.name) @
print(c.name)
Screen
Susannah @
Leah @
Leah
In the above example, it may look as though we have switched Mary's best friend from Susannah to Leah, but what we have really done is changed Susannah's name to be Leah, so now there are two Leahs. To prevent this sort of thing, some programming languages have private member variables that cannot be changed outside of the code for the class. Python does not have true private variables, so we just need to be a little more careful to avoid these kinds of mistakes.
Section 4.4 Name:____________________
Other Objects as Member Variables
Score:      /5
Problems
- Based on the following code:
class Person: @@
~def __init__(self, name): @
~~self.name = name @@
~def set_relationships(self, best_friend, worst_enemy): @
~~self.bf = best_friend @
~~self.we = worst_enemy
a = Person("Mary") @
b = Person("Susannah") @
c = Person("Jack") @
d = Person("Fred") @
a.set_relationships(b,c) @
b.set_relationships(a,d) @
c.set_relationships(a,b) @
d.set_relationships(c,a) @
What will print to the screen?
- print(a.bf.bf.name)
- print(b.we.we.name)
- print(c.bf.we.bf.name)
- print(d.we.we.bf.bf.name)
- Based on the following code:
class Car: @@
~def __init__(self, name, year): @
~~self.name = name @
~~self.year = year @@@
a = Car("Jeep",2009) @
b = Car("BMW 3 Series",2011) @
c = Car("Corolla",2003) @
d = b @
e = c @
f = d @
g = f @
e.year = 2014 @
g.year = 1990
What are a.year, b.year and c.year equal to?
- Write code for two classes: a Product and a Company.
- The Product should have a name and a price.
- The Company should have a name and a top product member variable that is an object of the Product class.
- Write __init__ methods for each.
- Write code to create a Product object and a Company object that has the Product object as its top product.
Class Code
Object Code
Section 4.5: Inheritance
The concept of inheritance can be one of the more difficult computer science concepts to understand. However, the Python code needed to use inheritance is not very complicated. The idea behind inheritance is that one class "inherits" all of the member variables and methods from another class. The class being inherited from is called the base class (sometimes also called the parent class or super class). The new class that inherits from the old class is called the subclass. Base classes should be thought of as more general classes than subclasses. Note: In the example below, the __init__ method appears in different places than we have seen before. This will be explained towards the end of the section.
A Student Subclass
class Person: @@
~def __init__(self, name, age): @
~~self.name = name @
~~self.age = age @@@
class Student(Person): @@
~def __init__(self, name, age, gpa): @
~~Person.__init__(self, name, age) @
~~self.gpa = gpa @@@@
per = Person("Chris", 74) @
stu = Student("Frank", 15, 3.1)
In the code above, the Student class (subclass) inherits member variables from the Person class (base class). In the code, we know that the Student class inherits the Person class because the word Person is in parenthesis next to the word Student. This is all that we are required to write to inherit one class from another. However, in the example above we also use the __init__ method of the base class, explained later.
In the example above, when we create a Student object of the Student class, we also create an object with all of the traits that a Person object has, in this example: name and age. But, the Student object also has a variable for gpa.
Because the subclass inherits all of the traits of the base class, the subclass is also the same type of thing as the base class. All students are also people. However, the reverse is not always true. Not all things that the base class can represent may be represented by a subclass: Not all people are students. For example we could include an Employee class that also inherits from the Person class. Most full time employess are not also full time students, but all are people.
A Employee Subclass
class Employee(Person): @@
~def __init__(self, name, age, weekly_hours_worked): @
~~Person.__init__(self, name, age) @
~~self.weekly_hours_worked = weekly_hours_worked @@@@
emp = Employee("Jack", 33, 46)
Base and Possible Subclasses
Base Class | Possible Subclasses |
Person | Student, Employee |
Student | ArtMajor, EnglishMajor, MathMajor |
Mammal | Person, Cat, Whale |
Animal | Mammal, Bird, Reptile, Fish |
LivingThing | Animal, Plant, Fungus, Bacterium |
MotorVehicle | Car, Truck, Motorcycle, Bus |
GUIComponent | CheckBox, ScrollBar, Button |
Note: In the last row, GUI stands for Graphical User Interface, which is the topic of the last chapter.
Inherited Methods
The rule that Python follows is fairly simple: when a method is called, Python first looks for the method in the code for the subclass, if it cannot find it, Python searches the code for base class. In a sense, Python looks up the most up-to-date method with that name. This is also true of the __init__ method and other special methods.
Some Animals
class Animal: @@
~def __init__(self, species_name, number_of_legs): @
~~self.species_name = species_name @
~~self.number_of_legs = number_of_legs @@
~def make_noise(self): @
~~print("Noise") @@@
class Dog(Animal): @@
~def __init__(self, name, coat_color): @
~~Animal.__init__(self, "Canis Lupus Familiaris", 4) @
~~self.name = name @
~~self.coat_color = coat_color @@
~def make_noise(self): @
~~print("Bark") @@@
class Cat(Animal): @@
~def __init__(self, name, coat_color): @
~~Animal.__init__(self, "Felis Catus", 4) @
~~self.name = name @
~~self.coat_color = coat_color @@
~def make_noise(self): @
~~print("Meow") @@@
ani = Animal("Orcinus Orca", 0) @
dog = Dog("Rover", "Brown") @
cat = Cat("Spot", "Yellow") @@
ani.make_noise() @
dog.make_noise() @
cat.make_noise()
Screen
Noise @
Bark @
Meow
Inheriting __init__
When we inherit an older class as part of creating a new class, we often want to use our previously programmed __init__ method so that we do not need to rewrite the same code. However, usually we only call the __init__ method when an object of the original (base) class is created. In this case, we need to write the code for creating an object of our new class, not the original class. So we must call the original __init__ method of the base class in a different way. The syntax is a little complicated but looks like: BaseClass.__init__(self,...). We call the older __init__ method of the base class inside of our new __init__ method, usually first. Take a look at the __init__ methods of all of the previous examples.
Inheritance Hierarchy
A subclass can itself be a base class for other classes. The resulting hierarchies of base classes and subclasses in some libraries of code can be quite complicated. An example of this is the Qt library, which is used primarily for creating graphical user interfaces (GUIs). A GUI is the type of computer program we are accustom to using, programs with scroll bars, buttons, menus, etc. In the Qt library, QPushButton is a class for a simple button in a GUI. QPushButton inherits QAbstractButton, which inherits QWidget, which inherits QObject. QPushButton itself is the base class of a class called QCommandLinkButton. All of this just to create a button for a GUI! Luckily, we don't usually need to worry about all of these details when we write code that uses a library like Qt.
It is not hard to imagine other hierarchies of possible subclasses. For example: An ArtMajor is a Student; A Student is Person; A Person is a Mammal; A Mammal is an Animal; An Animal is a LivingThing. Potentially, we could have a class hierarchy with LivingThing at the top of the hierarchy and ArtStudent near the bottom of the hierarchy with all of the other types in between. Of course, depending on the program we are trying to create, we may or may not need a complex hierarchy of classes.
A, Not So Practical, Class Hierarchy
Section 4.5 Name:____________________
Inheritance
Score:      /5
Problems
- Circle the class that would make the most sense as the base class (parent class):
Class One | Class Two |
Astronaut | Person |
Student | HighSchoolStudent |
Candy | Skittle |
Popcorn | Food |
Plague | Virus |
Tree | Plant |
Square | Rectangle |
- Think up another subclass of the Person class from example 1. Your class should have at least one additional member variable that the original Person class does not have. Make sure to create a new __init__ method that uses the original __init__ method. Also, make one object of your new class.
Code:
class Person: @@
~def __init__(self, name, age): @
~~self.name = name @
~~self.age = age
Your Class Code:
Your Object Code:
- Create a TractorTrailer subclass of the MotorVehicle class below. Your class should have a cargoCount variable that is initialized in the __init__ method. Make sure to create a new __init__ method that uses the original __init__ method. Make one object of your new class.
class MotorVehicle: @@
~def __init__(self, wheel_count, gas_mileage): @
~~self.wheel_count = wheel_count @
~~self.gas_mileage = gas_mileage @
Your Class Code:
Your Object Code:
- Create a heirarchy of base classes and subclasses with at least 6 classes. The heirarchy does not need to be linear. You do not need to write code, but draw a diagram. For example: Grasshoppers and LabyBugs are Insects, which are Animals. Robins are Birds, which are Animals.