7. Objects and Classes¶
7.1. Preliminary Exercises¶
7.1 Definition, Creation and Use of Objects
The code below defines a class Coin that is subsequently used to create an object called my_coin.
import random
# class Coin simulates a coin that can be flipped.
class Coin:
def __init__(self):
# The __init__ method initializes the
# __sideup data attribute with 'Heads'.
self.__sideup = 'Heads'
def toss(self):
# The toss method generates a random number in the range of 0 through 1.
# If the number is 0, then sideup is set to 'Heads',
# Otherwise, sideup is set to 'Tails'.
if random.randint(0, 1) == 0:
self.__sideup = 'Heads'
else:
self.__sideup = 'Tails'
def get_sideup(self):
# The get_sideup method returns the value of __sideup.
return self.__sideup
def main():
print("I am going to create a coin object ...")
my_coin = Coin()
print('This side is up:', my_coin.get_sideup())
print('I am going to toss the coin five times:')
for count in range(1, 6):
my_coin.toss()
print("After toss", count, "side", my_coin.get_sideup(), "is up.")
main()
1a Predicting and understanding the output
Please copy the code into IDLE and run it. Can you predict and understand why you get the output?
Here is the output of a sample run:
I am going to create a coin object ...
This side is up: Heads
I am going to toss the coin five times:
After toss 1 side Tails is up.
After toss 2 side Heads is up.
After toss 3 side Heads is up.
After toss 4 side Tails is up.
After toss 5 side Tails is up.
1b Observing and understanding the output
Copy the code into a visualiser (e.g. https://pythontutor.com/visualize.html#mode=edit), and step through the code .
Please observe:
how in line 25 the call Coin() causes method __init__(self) on line 5 to be run;
that after method __init__(self) has finished, variable my_coin references a Coin object (a.k. Coin instance);
that my_coin has one attribute ‘__sideup’ that has an initial value of “Heads”;
every time my_coin.get_sideup() is called, the current value of attribute __sideup is returned.
7.2 Objects as parameters
The code below defines a class Superstar that has getters and setters to allow access to its attributes.
class Superstar:
def __init__(self, real, nick, hit):
self.__real_name = real
self.__nick_name = nick
self.__latest_hit = hit
def get_real_name(self):
return self.__real_name
def get_nick_name(self):
return self.__nick_name
def set_nick_name(self, new_nick_name):
self.__nick_name = new_nick_name
def get_latest_hit(self):
return self.__latest_hit
def set_latest_hit(self, new_hit):
self.__latest_hit = new_hit
def get_latest_hit(self):
return self.__latest_hit
def show_latest_hit(star):
name = star.get_real_name()
nick = star.get_nick_name()
hit = star.get_latest_hit()
print('The latest hit of', nick, '(' + name + ') is: "' + hit + '"')
def main():
star1 = Superstar('Britney Spears', 'Queen B', 'Baby One more Time')
star2 = Superstar('Justin Bieber', 'JB', 'What Do You Mean?')
print()
show_latest_hit(star1)
show_latest_hit(star2)
star1.set_latest_hit('Hold Me Closer')
star2.set_latest_hit('Honest')
print()
show_latest_hit(star1)
show_latest_hit(star2)
main()
2a Predicting and understanding the output
Please copy the code into IDLE and run it. Can you predict and understand why you get the output?
2b Observing and understanding the output
Copy the code into a visualiser (e.g. https://pythontutor.com/visualize.html#mode=edit), and step through the code.
Please observe that:
in line 26 method get_real_name(), gives (read) access to attribute __real_name
in line 27 method get_nick_name(), gives (read) access to attribute __nick_name;
in line 28 method get_latest_hit(), gives (read) access to attribute __latest_hit;
method set_nick_name(), enables outside code to change attribute __nick_name;
method set_latest_hit(), enables outside code to change attribute __latest_hit;
that in lines 35 and 41 object star1 is used as parameter in function call show_latest_hit();
that in lines 36 and 42 object star2 is used as parameter in function call show_latest_hit();
7.2. Using Classes¶
7.3 using a class (BMI)
In the code fragment below a Person class has been defined.
class Person:
def __init__(self, name, age, weight, height):
self.__name = name
self.__age = age
self.__weight = weight
self.__height = height
def getBMI(self):
bmi = self.__weight / (self.__height * self.__height)
return bmi
def getStatus(self):
bmi = self.getBMI()
if bmi < 18.5:
return "Underweight"
elif bmi < 25:
return "Normal"
elif bmi < 30:
return "Overweight"
else:
return "Obese"
def getName(self):
return self.__name
def getAge(self):
return self.__age
def getWeight(self):
return self.__weight
def getHeight(self):
return self.__height
def main():
# Your code:
# create person with fields "John", 20, 75 and 1.70
# print persons properties and BMI status
# create person with fields "Mary", 19, 60 and 1.68
# print persons properties and BMI status
# create person with fields "Bill", 55, 100 and 1.80
# print persons properties and BMI status
main()
Use the class definition to create three persons in function main().
Get the following output by caling the methods of the Person class.
The output should look like this sample run:
>>>
=== RESTART: /Users/peterlambooij/Solutions/6.1.py
John is 20 years old, weighs 75 kg, is 1.7 m tall, BMI is 26
John is Overweight
Mary is 19 years old, weighs 60 kg, is 1.68 m tall, BMI is 21
Mary is Normal
Bill is 55 years old, weighs 100 kg, is 1.8 m tall, BMI is 31
Bill is Obese
>>>
7.4 The Account class
4a Program a class named ‘Account’ that contains:
A private int attribute named ‘id’ for the account.
A private float attribute named ‘balance’ for the account.
A private float attribute named ‘annualInterestRate’ that stores the current interest rate.
A constructor that creates an account with the specified id, initial balance, and annual interest rate.
A get method for attributes ‘id’, ‘balance’ and ‘annualInterestRate’.
A method named ‘getAnnualInterest’ that calulates and returns the annual interest of the account (based on the current balance).
A method named ‘deposit’ that deposits a specified amount to the account.
A method named ‘withdraw’ that withdraws a specified amount from the account, provided that the balance is sufficient.
Note that annualInterestRate is a percent (like 4.5%). You need to divide it by 100 for interest calculations.
4b Test the class with a test program that creates an Account object with an account id of 1122, a balance of 20000 euros, and an annual interest rate of 4.5%. Use the withdraw method to withdraw 2500 euros, use the deposit method to deposit 3000 euros, and print the id, balance and annual interest. Here is a sample output:
My account has:
id = 1122
balance = 20500
annual interest = 922.5
7.5 getters and setters (TV)
Below is the (partial) code of the definition of a TV class together with some test code. The class definition is not complete. All the methods still have to be implemented. Complete the methods with the information in the __init__ method and the descriptions in the methods themselves. The class can be tested with the test code in ‘main()’.
Here is the reference output of the solution:
>>>
=== RESTART: Solutions/w6/6.2.py ===
tv1's channel is 30 and volume level is 3
tv2's channel is 3 and volume level is 2
>>>
Here is the starting code:
class TV:
def __init__(self):
self.channel = 1 # the TV has a channel between 1 and 120
self.volumeLevel = 1 # the TV has a volume between 1 and 7
self.on = False # the TV is On (True) or Off (False)
def turnOn(self):
# your code
def turnOff(self):
# your code
def getChannel(self):
# your code
def setChannel(self, channel):
# your code, the channel must be between 1 and 120
def channelUp(self):
# your code, the channel should go up by one,
# if it stays between 1 and 120
def channelDown(self):
# your code, the channel should go down by one,
# if it stays between 1 and 120
def getVolume(self):
# your code
def setVolume(self, volumeLevel):
# your code, the volume must stay between 1 and 7
def volumeUp(self):
# your code, the volume should go up by one,
# if it stays between 1 and 7
def volumeDown(self):
# your code, the volume should go down by one,
# if it stays between 1 and 7
def main():
tv1 = TV()
tv1.turnOn()
tv1.setChannel(30)
tv1.setVolume(3)
tv2 = TV()
tv2.turnOn()
tv2.channelUp()
tv2.channelUp()
tv2.volumeUp()
print("tv1's channel is", tv1.getChannel(),
"and volume level is", tv1.getVolume())
print("tv2's channel is", tv2.getChannel(),
"and volume level is", tv2.getVolume())
main() # call the main() function
7.6 getters and setters (Fan)
6a Implement a class named Fan to represent a fan. The class contains:
A private int attribute named speed that specifies the speed of the fan (1, 2 or 3).
A private bool attribute named on that specifies whether the fan is on (the default is False).
A private float attribute named radius that specifies the radius of the fan.
A private string attribute named color that specifies the color of the fan.
The accessor (get) and mutator (set) methods for all four attributes.
A constructor (__init__) that creates a fan with the specified speed (default 1), radius (default 5), color (default blue), and on (default False).
6b Write a small program that creates a fan with:
def main():
myFan = Fan()
# your test code here ...
Use object myFan to test:
if after creation the values of the attributes are indeed 1, 5, blue and False (you can do this by calling the get methods)
if you can change each attribute with the set method and get back the changed value with get (for instance, after setSpeed(3) method getSpeed() should return 3 and not 1). Check this for all four attributes.
if you cannot set the attributes to an illegal value (for instance, radius must be greater than zero, so setRadius(-2) should not change value radius).
finally, test in interactive mode that you cannot access the private fields directly (for instance, print(myFan.speed) should fail, whereas print(myFan.getSpeed()) should be fine)
7.7 Stopwatch
7.a Implement a class named StopWatch. The class contains:
The private attributes startTime and endTime with get methods.
A constructor (__init__) that initializes startTime with the current time.
A method named start() that resets the startTime to the current time.
A method named stop() that sets the endTime to the current time.
A method named getElapsedTime() that returns the elapsed time for the stop watch in milliseconds.
You can find information on how to measure time in the 0HV120 Reader.
7.b Write a small program that creates a stopwatch with:
myStopWatch = StopWatch()
Use object myStopWatch to test:
if the startTime is indeed the current time after creation of myStopWatch
if start() resets startTime to the current time
if getElapsedTime() returns indeed the number of seconds after start() was called
if after stop() the getElapsedTime() does not increase anymore (the stopwatch has stopped)
Hint 1: use time.sleep() to control the amount of time between tests.
Hint 2: let the stopwatch return time in seconds since jan 1, 1970 (see reader). The test code can convert this into a readable format.
7.3. Combining Classes¶
7.8 Food Allergies
A restaurant wants to keep track of all food allergies of a group of guests, to determine which ingredients in a menu cannot be eaten by which person. You will build up the program starting with two classes and then using these to produce an overview of the needs of the group.
8a. Define class Allergy
Write the code for a class Allergy with private variables __name and __foods_to_avoid, and the following methods:
__init__(self, name, foods), with name the name of the allergy and foods a list of names of foods that should be avoided by a person with that allergy
add_food(self, food), adds food to the list of foods to avoid (if the food is not in the list yet)
remove_food(self, food), removes food from the list of foods to avoid. If food is not in the list, then nothing should happen.
get_foods(self), returns a list of all foods to avoid.
Make sure your code does not crash if parameter values are invalid. In that case the code should simply do nothing.
8b. Test class Allergy
Create a separate file and import the definition of 8a so you can test all the methods of Allergy.
7.9 The Car Factory
In this exercise you will program a simple administration of a car factory that consists of three parts (files) that each have their own responsibility:
a class Car, Responsibility: has all information about one individual car.
a class CarFactory, that contains a list of all cars currently in the warehouse, can report which cars of a certain make are available and that can produce new cars and deliver from warehouse.
a menu driven program through which the user (e.g. the factory manager) can interact with classes CarFactory and Car to initiate the actions of the factory. This last part to interact with the user has already been written and is shown below. You can use it as starter code for this exercise.
# car factory ui (menu driven program)
# responsibility: input/output from/to the user in order
# to use a CarFactory object
from car_factory import CarFactory
def print_menu():
print()
print("available commands")
print("(p)roduce a number of cars")
print("(f)ind cars of a certain model and/or color in inventory")
print("(s)hip a car")
print("(q)uit")
def description_of(car):
return car.get_color() + " " + car.get_model()
def produce_cars(factory):
nr_of_cars = int(input("nr of cars to produce: "))
cars_produced = factory.produce_cars(nr_of_cars)
print()
if cars_produced == []:
print("No cars were produced")
else:
print("The following cars were produced:")
for n, car in enumerate(cars_produced):
print(n+1, description_of(car))
def find_in_inventory(factory):
model = input("model (<enter> = all models): ")
color = input("color (<enter> = all colors): ")
cars_found = factory.find_cars(model, color)
print()
if cars_found == []:
print("No cars of the right model or color were found in the inventory.")
else:
print("The following cars were found in the inventory:")
for n, car in enumerate(cars_found):
print(n+1, description_of(car))
def ship_model_color(factory):
model = input("model to ship: ")
color = input("color to ship: ")
shipped_car = factory.ship_one_car(model, color)
print()
if shipped_car == None:
print("No", color, model, "was found in the inventory.")
else:
print("A", description_of(shipped_car), "was shipped.")
def main():
factory = CarFactory()
print("Welcome to the car factory")
print_menu()
command = ""
while command != 'q':
print()
command = input("command: ")
if "produce".startswith(command):
produce_cars(factory)
elif command == 'f':
find_in_inventory(factory)
elif command == 's':
ship_model_color(factory)
elif command == 'q':
print("Quitting the program ...")
else:
print("I did not recognise command '" + command + "'.")
print_menu()
main()
9.a Implement a class Car in a separate file car.py. The class should contain:
Private attributes model and color (of type string) with get methods.
A constructor (__init__) that initializes model and color.
- 9.b Implement the first part of class CarFactory in a separate file car_factory.py. This part should contain:
A private list inventory that contains car objects as its items.
A constructor (__init__) that initialises inventory to an empty list.
9.c Test whether the UI program can import CarFactory and give a menu:
Welcome to the car factory
available commands
(p)roduce a number of cars
(f)ind cars of a certain model and/or color in inventory
(s)hip a car
(q)uit
command:
9.d Add method produce_cars(nr_to_produce) to class CarFactory. The method should produce the required nr of Car objects and add them to the inventory. A list with the car objects that were produced should be returned to the caller.
The model and color are to be taken randomly from a list of available values. You can add these lines to the code of CarFactory:
available_models = ['Ford', 'Maseratti', 'Renault', 'Tesla', 'Volkswagen', 'Volvo']
available_colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']
Here is output of a sample run:
Welcome to the car factory
available commands
(p)roduce a number of cars
(f)ind cars of a certain model and/or color in inventory
(s)hip a car
(q)uit
command: p
nr of cars to produce: 10
The following cars were produced:
1 red Volvo
2 yellow Volkswagen
3 blue Renault
4 yellow Ford
5 orange Volvo
6 violet Tesla
7 yellow Maseratti
8 red Renault
9 yellow Volkswagen
10 indigo Tesla
9e Add method find_cars(model, color) to class CarFactory. The method should return a list of all car objects with the specified model and color. If the model is filled in but the color is left empty, then a list of all cars of that model should be returned. Similarly, if the model is left empty, and the color is specified, all models of that color need to be returned. Here is output of a sample run based on the inventory of 8.d:
command: f
model (<enter> = all models): Tesla
color (<enter> = all colors):
The following cars were found in the inventory:
1 violet Tesla
2 indigo Tesla
command: f
model (<enter> = all models):
color (<enter> = all colors): yellow
The following cars were found in the inventory:
1 yellow Volkswagen
2 yellow Ford
3 yellow Maseratti
4 yellow Volkswagen
command: f
model (<enter> = all models): Renault
color (<enter> = all colors): green
No cars of the right model or color were found in the inventory.
command: f
model (<enter> = all models): Renault
color (<enter> = all colors): red
The following cars were found in the inventory:
1 red Renault
9f Add method ship_one_car(model, color) to class CarFactory. If a car object with the specified model and color is in the inventory, then the Car object should be removed from the inventory and returned to the caller. If the model / color combination is not in the inventory then the value None should be returned. Here is the output of a sample run based on the inventory of 8.d:
command: s
model to ship: Renault
color to ship: green
No green Renault was found in the inventory.
command: s
model to ship: Renault
color to ship: red
A red Renault was shipped.
command: f
model (<enter> = all models):
color (<enter> = all colors):
The following cars were found in the inventory:
1 red Volvo
2 yellow Volkswagen
3 blue Renault
4 yellow Ford
5 orange Volvo
6 violet Tesla
7 yellow Maseratti
8 yellow Volkswagen
9 indigo Tesla
7.10 The Pet Shop
Build a pet shop administration that consists of three parts (files) that each have their own responsibility:
a class Animal, that has all information about one individual pet: name, species, gender and year_of_birth.
- a class Petshop, that contains a list of the pets (objects of class Animal) in store with the following additional responsibilities:
Petshop can report which species are available (returning a list of all animals of that species)
Petshop can add a new pet to its list. (It is the resonsibility of class Petshop when adding a new pet to check if its properties are valid e.g. no empty name, gender is “male”, “female” or “unknown”, year_of_birth is not in the future).
Petshop can sell an animal, based on matching name, species and year_of_birth.
a menu driven program through which the user (e.g. the shop owner) can interact with classes Pet and Shop to initiate the actions of the store.
Make your program and classes “robust” (no crashes regardless of input values of the user or parameter values of the methods).
This is in some way an open-ended exercise, the more realistic you make it, the more you will learn.
© Copyright 2022, dr. P. Lambooij
last updated: 09-10-2022