OOP Coursework
OOP Coursework
Students
1. STUDENT NAME (BANNER)
I have carefully read and fully understand Regulations 3.49–3.55 of Chapter 3 of the
Regulatory Framework of the University of the West of Scotland, which outline the rules and
policies regarding cheating and plagiarism.
I confirm that this assessment is the collective work of our group, consisting of five
members, with each member contributing to the completion of this task. Where applicable,
we have clearly referenced and acknowledged the contributions or ideas of others outside
our group.
I also affirm that no part of this assessment has been written, in whole or in part, by anyone
outside the group, except for explicitly referenced sources.
Furthermore, I confirm that this assessment has not been submitted previously, either
partially or fully, for any other module or academic purpose, ensuring it does not fall under
self-plagiarism.
This declaration is made with honesty and integrity in adherence to the University’s
academic standards.
2
Table of Contents
Week 1 - Introduction to Python Programming Language.......................................................6
Introduction to Python.........................................................................................................6
Understanding Python Syntax.......................................................................................... 6
Getting Started..................................................................................................................... 6
Installing the Python Interpreter and Editor.....................................................................6
Core Concepts and Practical Exercises..................................................................................8
Exercise 1: Variables, Types, and Casting..........................................................................8
Exercise 2: Arithmetic Operators......................................................................................9
Exercise 3: Strings and f-Strings......................................................................................10
Final Task: Temperature Converter Program...................................................................11
Week 2 - Introduction to Python Programming II...................................................................12
Expanding Our Python Fundamentals................................................................................12
Comparisons and Conditional Statements.........................................................................12
Implementation: Comparing Temperatures...................................................................13
Python Lists: Data Structures and Manipulation................................................................13
Python Loops......................................................................................................................14
Obtaining User Input.......................................................................................................... 16
Week 3 - Python Functions, Scope, and Errors.......................................................................19
Introduction to Modular Programming..............................................................................19
Python Functions................................................................................................................19
Returning Values and Mathematical Computations.......................................................20
Variable Scope and Assertions........................................................................................21
Identifying and Fixing Common Errors...............................................................................21
Final Project: To-Do List Manager.......................................................................................22
Week 4 - Object Oriented Programming: Classes and Objects...............................................25
Class:.................................................................................................................................. 25
Object:................................................................................................................................25
Classes Overview................................................................................................................28
Summary............................................................................................................................ 29
Portfolio 1 and 2 Solution...................................................................................................30
Week 5 - Inheritance & Polymorphism...................................................................................38
Inheritance......................................................................................................................... 38
3
Polymorphism.................................................................................................................... 38
Multiple Inheritance...........................................................................................................39
Conceptual Example for Multiple Inheritance:...............................................................39
Polymorphism Details.........................................................................................................41
Types of polymorphic functions:....................................................................................41
Kwargs............................................................................................................................ 42
Args................................................................................................................................ 43
Generics (Duck Typing)...................................................................................................43
Week 6 - Programming Paradigms......................................................................................... 45
Procedural Programming....................................................................................................45
Functional Programming....................................................................................................46
Characteristics................................................................................................................ 46
Object-Oriented Programming (OOP).................................................................................47
Characteristics................................................................................................................ 47
Key Concepts.................................................................................................................. 47
Portfolio Exercise Lab Week 6 (Implementing Persistence)................................................50
Week 7 - SOLID Principles and Python Exceptions.................................................................56
The SOLID Principles...........................................................................................................56
Python Exception Handling.................................................................................................... 57
Putting It Together: Refactoring the To-Do App..............................................................57
Week 8 - Debugging, Properties, and Persistence..................................................................62
System Debugging and Error Tracing..................................................................................62
Implementation: Fixing the Vehicle Simulator Bug.........................................................63
Python Properties using the @property Decorator............................................................63
Implementing Persistence with CSV...................................................................................64
Implementation: The CSV Data Access Object................................................................64
Week 9 - Data Structures & Abstract Classes..........................................................................67
What is a Data Structure?...................................................................................................67
Types of Data Structures.................................................................................................67
Abstract Classes..................................................................................................................70
Portfolio Exercise 6......................................................................................................... 71
4
Table of Figures
Figure 1 Visual Studio Code Installation...................................................................................7
Figure 2 Python VS Code Extension..........................................................................................7
Figure 3 Multiple Inheritance.................................................................................................40
Figure 4 Debugging in Jupyter Notebook...............................................................................62
Figure 5 Debugging options....................................................................................................62
5
Week 1 - Introduction to Python
Programming Language
Introduction to Python
Python is a high-level, dynamic, and incredibly versatile programming language utilized
globally across domains such as web development, artificial intelligence, data analytics, and
workflow automation. The practical lab exercises presented in this document serve as our
primary introduction to Python. By successfully engaging with and completing this week’s
class, we aim to implement our very first Python procedural programs and build a robust
understanding of how to interpret and write using Python syntax.
Getting Started
Before we can begin drafting code, we must construct our development environment by
setting up the necessary software components.
6
Figure 1 Visual Studio Code Installation
7
Executing Python Scripts
During our setup phase, we learned that a Python script can be executed in multiple ways,
yielding the same output results.
1. Using the VS Code Interface: You can run the file in VSCode by clicking the “Play”
button at the top right hand corner.
2. Using the Command Line Interface (CLI): We can use the command line to run our
Python scripts as well. We open our terminal window, navigate to the directory
where our script is saved using the cd command, and then type the execution
command. For example, the command to run our lab file is lab_week_1.ipynb.
# Using the type() function to verify and print the data types
print(type(is_session_active))
print(type(participant_count))
print(type(mathematical_pi_approx))
print(type(welcome_phrase))
8
# Casting the variables to alter their data types
casted_to_float_val = float(base_numeric_val)
casted_to_integer_val = int(decimal_measurement)
casted_bool_to_int_val = int(logical_flag)
Output:
9
total_plot_area = plot_length_meters * plot_width_meters
10
print(formatted_biography)
Output:
11
Week 2 - Introduction to Python
Programming II
Expanding Our Python Fundamentals
Following our initial setup and exploration of basic syntax, our group continued our
introduction to programming by exploring more complex Python constructs. The primary
goal for this week's coursework was to understand how to build dynamic programs that can
handle conditional routing, iterate through data sets using loops, manage collections of
items via lists, and successfully pause execution to accept and process user input.
== Equal to 5 == 5 (True)
2. Logical Operators: Logical operators allow for the combination of multiple Boolean
expressions to create complex conditions:
a. and: Returns True only if both operands are true.
b. or: Returns True if at least one operand is true.
c. not: Reverses the Boolean result (e.g., not True becomes False).
3. Conditional Statements (if, elif, else): Python uses these statements to control the
flow of execution. Code blocks within these statements must be indented to indicate
they belong to that specific condition.
a. if Statement: The primary check. If the condition is True, the indented code
block executes.
12
b. elif (else-if): Acts as a secondary check. It is only evaluated if the preceding if
(or elif) condition fails.
c. else: The "catch-all" statement. It executes only if none of the previous
conditions in the chain were met.
13
2. Essential Operations: To manage the collection effectively, we utilized several built-in
Python techniques:
a. Slicing: Used to extract a specific sub-range of elements.
i. Syntax: list[start:end] (Note: the end index is exclusive).
b. Reassignment: Modifying an existing item by targeting its specific index (e.g.,
colors[0] = "Crimson").
c. Appending: Using the .append() method to add a single new element to the
very end of the structure.
d. Membership Testing: Using the in keyword to verify if a specific item exists
within the list.
e. Length Checking: Using the len() function to determine the total number of
items in the collection.
3. Practical Implementation: The following script demonstrates these concepts by
managing a collection of paint colors:
# 4. Slicing a sub-list
# Extracts elements from index 1 up to (but not including) index
3
selected_shades = paint_colors[1:3]
# 5. Programmatic Addition
paint_colors.append("Emerald")
Output:
Python Loops
To automate repetitive tasks, Python offers looping mechanisms. Our group explored two
primary loop structures:
1. While Loops: These loops continuously execute a block of code as long as a specified
condition remains true. We noted that failing to update the evaluation variable inside
the loop results in a fatal infinite loop.
14
2. For Loops: These loops are designed to iterate sequentially over a collection of items,
such as our previously discussed lists.
We paired our for loops with the built-in range() function, which rapidly generates a series of
numbers within a designated boundary. To establish fine-grained control over our iterations,
we practiced implementing the break keyword (to immediately terminate the loop entirely)
and the continue keyword (to skip the remainder of the current iteration and proceed to the
next).
Implementation: Even Numbers, Sum of Squares, and Countdowns
We consolidated the three loop-based tasks into a single execution flow. This involved
filtering numbers using the modulus operator, utilizing mathematical exponents inside a
bounded range, and managing a decrementing variable for a while loop.
This code segment demonstrates iterating through a static list to identify even values,
calculating an accumulated sum of squared integers, and executing a while loop that ticks
down to zero.
15
Obtaining User Input
A program is vastly more useful when it dynamically interacts with its user. We utilized
Python's native input() function, which pauses script execution and waits for the user to
type a response via the console. Because the input() function inherently treats all received
data as strings, we learned the crucial step of type-casting (e.g., using int() or float())
whenever mathematical operations are required on the user's data.
Implementation: Interactive Age Categorization and Advanced Temperature Converter
To complete our coursework, we tackled the advanced tasks. First, we categorized a user's
age based on strict numerical thresholds. Following that, we achieved the extra task criteria
by building a highly dynamic temperature converter that prompts the user for both the
source unit (C, F, or K) and the numerical value, subsequently applying the correct
mathematical formulas through a nested if-elif structure.
The following code requires terminal interaction. It captures user inputs, casts them to their
appropriate data types, and routes the data through conditional logic to produce custom
outputs.
16
print("You are a senior citizen.")
if source_metric == "C":
calc_f = (source_temp_value * 9/5) + 32
calc_k = source_temp_value + 273.15
print(f"Fahrenheit: {calc_f}°F")
print(f"Kelvin: {calc_k}K")
else:
print("Error: Unrecognized unit. Please restart and use C, F,
or K.")
Output:
17
18
Week 3 - Python Functions, Scope,
and Errors
Introduction to Modular Programming
As our group progressed into the third week of our Python coursework, we shifted our focus
from writing linear scripts to developing modular, reusable, and robust code. The core
objective of this week's laboratory exercises was to master the implementation of custom
functions, understand variable scope, implement assertions for quality assurance, and
systematically identify and fix common programming errors. The culmination of this week's
effort was the collaborative development of a fully functional To-Do List application.
Python Functions
A function represents a dedicated block of code engineered to perform a specific task only
when explicitly called. This prevents code duplication and drastically improves readability.
Throughout our exercises, we identified several key characteristics of functions:
Definition: Functions are declared utilizing the def keyword followed by a unique
identifier and parentheses.
Parameters vs. Arguments: Parameters are the temporary variables defined within
the function's signature, whereas arguments are the actual data values injected into
the function upon execution.
Keyword Arguments & Default Values: Python allows us to pass arguments explicitly
by name (bypassing strict positional order) and establish default fallback values
within the parameter list if an argument is omitted by the user.
Implementation: Iterating Through Collections via Functions
To demonstrate our comprehension, we constructed a function designed to iterate through
a predefined list of names and output a personalized greeting for each entry.
The code block below defines a function that accepts a list as a parameter, utilizes a standard
for loop to traverse the collection, and prints a formatted greeting using an f-string.
19
Output:
20
# Executing the function with the assignment parameters
final_portfolio_value = evaluate_investment_growth(1000, 5, 0.03)
print(f"\nFinal returned integer value: £
{final_portfolio_value}")
Output:
# If the function does not equal 1159, the program will crash
here
assert evaluate_investment_growth(1000, 5, 0.03) == 1159
21
Identifying and Fixing Common Errors
A significant portion of our lab time was dedicated to debugging intentional semantic errors.
Python raises explicit flags when a program violates strict constraints. Our group reviewed
the broken code snippets, identified the core issues, and refactored the code to run
smoothly.
Syntax Error Fix: We corrected the misspelled pritn built-in function back to print.
Name Error Fix: We initialized the variable favorite_color before attempting to call it.
Value Error Fix: We type-casted the string "5" into an int() to permit mathematical
addition.
Index Error Fix: We requested index 1 to fetch the second array element, avoiding
the out-of-bounds index 3.
Indentation Error Fix: We properly indented the conditional print statement using
the tab key to align it within the if block.
22
Final Project: To-Do List Manager
As our task for Week 3, we engineered a fully functional, interactive To-Do List application.
This program aggregates everything we have learned: while loops for continuous execution,
conditional if-elif statements for menu routing, list manipulation for data storage, and
custom functions to handle dedicated operations.
The code below establishes an empty global list and relies on custom functions triggered by
a main application loop. It features input sanitation to prevent crashes if a user attempts to
remove an item that does not exist.
23
# This block continuously prompts the user until they explicitly
trigger the 'break' command
print("Initializing To-Do List Manager...")
while True:
print("\n--- Main Menu ---")
print("1. Add a new task")
print("2. View all tasks")
print("3. Remove a task")
print("4. Quit Application")
if user_menu_selection == "1":
register_new_task()
elif user_menu_selection == "2":
display_current_tasks()
elif user_menu_selection == "3":
strike_task()
elif user_menu_selection == "4":
print("\nShutting down To-Do Manager. Goodbye!")
break
else:
print("Invalid choice. Please verify your input and try
again.")
Output:
24
25
Week 4 - Object Oriented
Programming: Classes and Objects
In Python, classes and objects are fundamental concepts used to implement object-oriented
programming. A class serves as a blueprint for creating objects, which are instances of that
class. Here’s a breakdown of each concept with examples.
Class:
A class in Python is a blueprint that defines attributes (data) and methods (functions) that
characterize a specific type of object. Think of a class as a template for something you want
to create. For example, if you want to represent a "Dog," you’d create a `Dog` class with
attributes like `name` and `breed`, and methods like `bark()`.
Object:
An object is an instance of a class. When you create an object, you are creating an individual
instance of the class with unique attribute values. Each object is distinct but follows the
blueprint defined by its class.
Example code for a Dog class and objects
class Dog:
# Constructor method to initialize attributes
def __init__(self, name, breed):
[Link] = name # Attribute: name of the dog
[Link] = breed # Attribute: breed of the dog
# Creating Objects:
# Creating two Dog objects with different names and breeds
dog1 = Dog("Buddy", "Golden Retriever")
dog2 = Dog("Milo", "Beagle")
class Dog:
26
# Constructor method to initialize attributes
def __init__(self, name, breed):
[Link] = name # Attribute: name of the dog
[Link] = breed # Attribute: breed of the dog
# Creating Objects:
# Creating two Dog objects with different names and breeds
dog1 = Dog("Buddy", "Golden Retriever")
dog2 = Dog("Milo", "Beagle")
Output:
Python by-default supports multiple inheritance. When a class is derived from more than
one base class is called Multiple Inheritance. The derived class inherits all the features and
properties that base class has.
Explanation:
`Dog` is a class that defines the attributes `name` and `breed` and has a method
`bark()`.
`dog1` and `dog2` are objects (instances) of the `Dog` class.
`dog1` has `name = "Buddy"` and `breed = "Golden Retriever"`.
`dog2` has `name = "Milo"` and `breed = "Beagle"`.
Both `dog1` and `dog2` use the `bark()` method, but their output differs based on
their `name` attribute
Here is an example of a Python program that implements multiple inheritance using animal-
related classes. This program includes a base class called Animal, derived classes such as
27
Flying, Swimming, and Walking, and a hybrid class named Penguin that combines multiple
behaviours
Another Example: Class and Object in Practice
class Animal:
"""
The base class for all animals.
Attributes:
name (str): Name of the animal.
"""
class Flying:
"""
A mixin class representing flying capability.
"""
class Swimming:
"""
A mixin class representing swimming capability.
"""
class Walking:
"""
A mixin class representing walking capability.
"""
28
"""Indicates that the animal can walk."""
return f"{[Link]} is walking on land!"
# Example Usage
if __name__ == "__main__":
peng = Penguin("Peng")
print([Link]())
print([Link]())
print([Link]())
print(peng.who_am_i())
Output:
Classes Overview
1. Animal:
o Base class for all animals.
o Contains the breathe method to indicate the animal's breathing behavior.
o Attributes:
name (str): The name of the animal.
2. Flying:
o Mixin class representing the ability to fly.
29
o Contains the fly method to indicate flying behavior.
o Assumes the name attribute exists (provided by another parent class).
3. Swimming:
o Mixin class representing the ability to swim.
o Contains the swim method to indicate swimming behavior.
o Assumes the name attribute exists (provided by another parent class).
4. Walking:
o Mixin class representing the ability to walk.
o Contains the walk method to indicate walking behavior.
o Assumes the name attribute exists (provided by another parent class).
5. Penguin:
o Combines functionality from Animal, Swimming, and Walking.
o Contains the who_am_i method to describe the penguin's unique features.
o Overrides the __init__ method to initialize the animal's name.
Summary
Key Features of Object-Oriented Programming
1. Encapsulation: Combining data and methods within a class to protect the integrity of
the data.
2. Abstraction: Concealing complex implementation details from the user to simplify
interaction.
3. Inheritance: Allowing a class to inherit properties and behaviors from another class,
promoting code reuse.
4. Polymorphism: Enabling methods to behave differently based on the object that calls
them.
30
Portfolio 1 and 2 Solution
view_options(): This function displays a menu of options for interacting with the task
management program.
propagate_task_list(task_list: TaskList) -> TaskList: This function populates the TaskList with
a set of sample tasks for testing or demonstration purposes.
main(): This is the primary function that runs the task management application. It initializes
a TaskList, populates it with sample tasks, and presents the user with options to manage
tasks interactively.
Docstrings: Each function has a docstring that describes its purpose, parameters, and return
types, providing a clear guide for understanding and modifying the code.
Type Hints: Type hints like -> None and -> TaskList specify expected return types for each
function, improving readability and helping with debugging.
Task
Explanation of Methods
__init__: Initializes a Task object with a title, due date, and description. The
date_created attribute is set to the current date and time when the task is created,
and completed is initially set to False.
change_description: Allows modification of the task's description.
mark_completed: Marks the task as completed by setting the completed attribute to
True.
change_title: Allows changing the title of the task.
__str__: Provides a formatted string representation of the task’s key attributes,
including title, creation date, due date, completion status, and description.
Tasklist
Explanation of Methods
31
__init__: Initializes the TaskList for a specified owner and sets up an empty tasks list
to hold individual tasks.
add_task: Adds a Task object to the tasks list.
remove_task: Removes a task at the specified index x. If the index is invalid (either
too low or too high), an error message is printed.
view_tasks: Displays the list of tasks along with their indexes. Each task is printed in
the format defined by the __str__ method of the Task class, which helps provide
detailed task information.
import datetime
class Task:
#
def mark_completed(self):
[Link] = True
def __str__(self):
32
status = "Not Completed"
[Link]
class TaskList:
[Link] = None
[Link] = owner
# [Link] = []
[Link] = []
def view_tasks(self):
for i, items in enumerate([Link]):
print(i, items)
def view_overdue_tasks(self):
new_date = [Link]()
overdue_task = [
task
for task in [Link]
if task.date_due < new_date and not [Link]
]
if overdue_task:
print("This tasks are Over-due")
for i, items in enumerate(overdue_task):
33
print(i, items)
else:
print("No over-due task available")
Implementation in [Link]
def view_options():
print("1. Add a new task to the list.")
print("2. View the current tasks in the list.")
print("3. Remove a task from the list.")
print("4. Mark a task as completed or \n Change due Date or
Change description).")
print("5. Change the title of a task.")
print("6. Task Overdue.")
print("7. Quit and exit the program.")
def main():
owner = Owner("Group4", "Group4@[Link]")
34
task_list = TaskList(owner)
try:
task_list.tasks = task_dao.get_all_tasks()
if not task_list.tasks:
task_list = list_options(task_list)
except FileNotFoundError:
task_list = list_options(task_list)
while True:
view_options()
choice = input("Please make a selection from (1-6): ")
if choice == '1':
new_task = input("Add your task please: ")
create_date = input("Set the date (YYYY-mm-dd): ")
describe = input("Write a description: ")
new_date = [Link](create_date,
"%Y-%m-%d")
task_list.add_task(Task(new_task, new_date,
describe))
if "complete" in action:
selected_task.mark_completed()
elif "due date" in action:
35
new_date_str = input("Type the new Date (YYYY-mm-
dd):")
selected_task.change_date_due([Link](new_date
_str, "%Y-%m-%d"))
elif "description" in action:
new_desc = input("Type the new description:")
selected_task.change_description(new_desc)
if __name__ == "__main__":
main()
Output:
36
3 Do homework (created: 2024-11-20 15:40:24.187931, due: 2024-11-
23 15:40:24.187931, completed: False,description:Do homework
4 Walk dog (created: 2024-11-20 15:40:24.187931, due: 2024-11-25
15:40:24.187931, completed: False,description:Walk dog
5 Do dishes (created: 2024-11-20 15:40:24.187931, due: 2024-11-26
15:40:24.187931, completed: False,description:Do dishes
Select a task to perform (Complete a task/ Change due Date/Change
description)?: Change description
Which of the above tasks description do you want to change:1
Type the new description:Do Laundry every saturday
1. Add a new task to the list.
2. View the current tasks in the list.
3. Remove a task from the list.
4. Mark a task as completed or
Change due Date or Change description).
5. Change the title of a task.
6. Task Overdue.
7. Quit and exit the program.
Please make a selection from (1-6): 2
0 Buy groceries (created: 2024-11-20 15:40:24.187931, due: 2024-
11-16 15:40:24.187931, completed: False,description:Buy groceries
1 Do laundry (created: 2024-11-20 15:40:24.187931, due: 2024-11-
22 15:40:24.187931, completed: False,description:Do Laundry every
saturday
2 Clean room (created: 2024-11-20 15:40:24.187931, due: 2024-11-
19 15:40:24.187931, completed: False,description:Clean room
3 Do homework (created: 2024-11-20 15:40:24.187931, due: 2024-11-
23 15:40:24.187931, completed: False,description:Do homework
4 Walk dog (created: 2024-11-20 15:40:24.187931, due: 2024-11-25
15:40:24.187931, completed: False,description:Walk dog
5 Do dishes (created: 2024-11-20 15:40:24.187931, due: 2024-11-26
15:40:24.187931, completed: False,description:Do dishes
1. Add a new task to the list.
2. View the current tasks in the list.
3. Remove a task from the list.
4. Mark a task as completed or
Change due Date or Change description).
5. Change the title of a task.
6. Task Overdue.
7. Quit and exit the program.
Please make a selection from (1-6): 6
This tasks are Over-due
0 Buy groceries (created: 2024-11-20 15:40:24.187931, due: 2024-
11-16 15:40:24.187931, completed: False,description:Buy groceries
1 Clean room (created: 2024-11-20 15:40:24.187931, due: 2024-11-
19 15:40:24.187931, completed: False,description:Clean room
37
1. Add a new task to the list.
2. View the current tasks in the list.
3. Remove a task from the list.
4. Mark a task as completed or
Change due Date or Change description).
5. Change the title of a task.
6. Task Overdue.
7. Quit and exit the program.
Please make a selection from (1-6):
In the output above, the description for the first task “Do laundry was Do laundry”
After option 4 was selected the system asked about the task to be performed. “Change
description was selected from the options” and the system asked again which task’s
description to be changed.
Task 1 do laundry was selected and the descripton was changed from “Do laundry” to ” Do
Laundry every saturday ”
Task option 6 also retrieved all overdue tasks.
38
Week 5 - Inheritance &
Polymorphism
In Week 5, we explored two of the core pillars of Object-Oriented Programming (OOP):
Inheritance and Polymorphism.
Inheritance
Inheritance is a powerful mechanism that allows a new class (the child or derived class) to
acquire the properties and behaviors of an existing class (the parent or base class). It is a
foundational OOP concept designed to promote code reusability, allowing developers to
leverage existing functionality and modify specific behaviors as needed. Notably, Python
natively supports Multiple Inheritance, a feature that is not available in many other popular
programming languages.
Polymorphism
Polymorphism translates to having "many forms." It refers to the ability to use the same
method or function name across different types of inputs or objects. While Python handles
polymorphism differently than strictly typed languages like Java or C# (it does not support
traditional method overloading), its dynamic nature allows functions to accept various
parameter types and return corresponding value types seamlessly.
Example Code for Inheritance
# Base class
class Vehicle:
def __init__(self, colour: str, weight: int, max_speed: int,
max_range: int | None = None, seats: int | None = None):
[Link] = colour
[Link] = weight
self.max_speed = max_speed
self.max_range = max_range
[Link] = seats
def get_total_seats(self):
if [Link] is not None:
return [Link]
return 0
39
def __init__(self, colour: str, weight: int, max_speed: int,
car_type: str, fuel_capacity: int = 0, max_range=None,
seats=None):
# Calling the parent class constructor
super().__init__(colour, weight, max_speed, max_range,
seats)
self.car_type = car_type
self.fuel_capacity = fuel_capacity
In this example, we define two classes: Vehicle and Car. The Vehicle acts as the base class,
serving as a starting template. The Car is inherited from the Vehicle class, meaning it
instantly gains all the properties and methods of the base class. When initializing the Car
class, we use super().__init__() to properly initialize the inherited parent attributes. Notice
how the move() function is overridden in the child class with custom print logic—this is a
classic practical use of inheritance.
Output:
Multiple Inheritance
By default, Python allows a class to inherit from more than one base class—a concept known
as Multiple Inheritance. The derived child class successfully absorbs all the features,
methods, and properties of every base class it is linked to.
40
Conceptual Example for Multiple Inheritance:
Imagine an Aquatic class that contains a swim method, and a Reptile class that contains a
walk method. If we create a Frog class that inherits from both Aquatic and Reptile, that Frog
object will be able to execute both the swim and walk actions.
class Aquatic:
def __init__(self, name: str, fins: int):
[Link] = name
[Link] = fins
[Link] = "Aquatic"
def swim(self):
print(f"{[Link]} is swimming under water with
{[Link]} fins")
class Reptile:
def __init__(self, name: str, limbs: int):
[Link] = name
[Link] = limbs
def walk(self):
print(f"{[Link]} is walking on the ground with
{[Link]} legs")
41
Aquatic.__init__(self, "Frog", 2)
# Explicitly calling the Reptile class constructor
Reptile.__init__(self, "Frog", 4)
def jump(self):
print(f"{[Link]} is jumping on the ground with
{[Link]} limbs")
frog1 = Frog()
This script translates the conceptual multiple inheritance picture into code. Because the Frog
class inherits from both Aquatic and Reptile, it successfully calls both swim() and walk(),
while also introducing its own unique jump() method.
Polymorphism Details
Polymorphism ensures that the same function or method name can adapt to different data
types or varying numbers of arguments.
42
# Fallback: if data types are invalid, return -1 as an error
code
else:
return -1
The Addition function acts differently depending on the input provided. It evaluates the
arguments and makes a processing decision: if the parameters are integers or floats, it
simply adds them. If they are numeric strings, it safely converts them to floats before adding.
If an unsupported data type is passed (like a boolean or list), it defaults to returning -1 to
indicate an invalid parameter.
Example Code for Pre-Defined Function
# Pre-defined functions
print("\nPre-Defined functions:")
# Takes a single argument
print("Hello")
# Takes multiple arguments of completely different types
print("Hello", 1234, False)
The Python interpreter is full of pre-defined polymorphic functions. The print() and type()
functions are perfect examples; they seamlessly accept everything from single string
43
arguments to multiple mixed-type parameters (ints, floats, bools, lists) without throwing
errors.
Kwargs
**kwargs is a special syntax allowing you to pass an arbitrary number of named (keyword)
arguments to a function. The function intercepts them and packages them into a dictionary.
This is particularly useful when passing settings or optional configurations.
Syntax & Example Code:
def multiple_args(**kwargs):
print("Type of **kwargs:", type(kwargs))
print(kwargs)
multiple_args(name="Prajwal", age=24)
Output:
Args
Similar to kwargs, *args is used to pass a variable number of unnamed arguments. Instead of
a dictionary, the function receives them as a sequence (tuple). You simply pass in comma-
separated values.
def sum_numbers(*args):
total = 0
for num in args:
total += num
return total
class Dog:
def make_sound(self):
print("Dog is barking")
class Cat:
44
def make_sound(self):
print("Cat is meowing")
class Wolf:
def make_sound(self):
print("Wolf is howling")
Learning Outcome
In Week 5, we focused on two of the most critical elements of Object-Oriented
Programming: Inheritance and Polymorphism.
Inheritance: Inheritance grants a child class the ability to adopt the features and
methods of a parent class, significantly streamlining code reuse. For instance, a Car
class can inherit common traits (like speed or color) from a Vehicle class while
establishing its own unique characteristics. Python goes a step further by supporting
multiple inheritance, allowing a Frog to combine the abilities of an Aquatic parent
and a Reptile parent.
Polymorphism: This concept dictates that a single method name can operate across
different data types. A great example is Python’s print() function, which intuitively
handles text, numbers, and arrays. While Python bypasses the traditional method
overloading seen in languages like C#, it keeps functions highly versatile through
dynamic typing.
*args and **kwargs: We learned specialized ways to make function inputs flexible:
o *args gathers multiple unnamed arguments into a sequence.
o **kwargs gathers named arguments into a dictionary.
Generics: Python's flexible nature means that different class objects can safely share
method names. Grouping a Dog, Cat, and Wolf together is perfectly valid; calling
make_sound() on each will trigger their respective behaviors automatically.
45
In short, we successfully learned how to implement inheritance for scalability, utilize
polymorphism for flexibility, and leverage specialized argument syntax for adaptable code.
46
Week 6 - Programming Paradigms
Programming paradigms dictate the fundamental style and methodology used to solve
problems through code. Below, we explore three primary paradigms: Procedural
Programming, Functional Programming, and Object-Oriented Programming (OOP).
Procedural Programming
Procedural programming is a paradigm centered around procedures, which are commonly
referred to as functions or routines. This methodology breaks down a program into smaller,
highly manageable pieces (functions) that execute when called. It relies on a linear, step-by-
step approach to accomplish tasks, and it fundamentally treats data and functions as
separate entities.
Characteristics
Top-Down Approach: Execution flows sequentially from the top to the bottom.
Simplicity: Highly straightforward and easy to implement for smaller applications.
Reusability: Functions can be invoked multiple times, significantly reducing code
duplication.
No Encapsulation: Data is not bound to specific functions, meaning it remains
exposed and separate from the behavior.
Example: Procedural Approach
efficiency = calc_efficiency(distance_traveled,
fuel_consumed)
show_vehicle_efficiency(vehicle_name, efficiency)
Output:
47
Information to Note
The procedural program effectively divides concerns into distinct functions. The data—such
as the vehicle's name, the distance traveled, and the fuel consumed—is explicitly passed
into these functions as arguments.
Advantages
Very simple to learn and implement for beginners.
Code execution is highly linear, making it easier to debug simple scripts.
Disadvantages
Becomes increasingly difficult to manage and scale for large programs.
Lacks encapsulation, offering no mechanisms for data hiding.
Highly prone to errors when multiple functions modify global or shared data.
Functional Programming
Functional programming treats functions as the absolute core building blocks of software
architecture. It relies heavily on immutable data and "pure" functions—meaning functions
that do not produce unpredictable side effects. This paradigm utilizes concepts such as
higher-order functions and operations like map, filter, reduce, and recursion. It champions a
declarative coding style, emphasizing what needs to be achieved rather than mapping out
exactly how to achieve it.
Characteristics
In functional programming, functions are considered "first-class citizens." This means they
can be passed as arguments, returned by other functions, and assigned directly to variables.
This paradigm rigidly avoids altering the state of existing data, promoting the exclusive use
of immutable data structures.
Example: Functional Approach
# Pure functions
def is_electric(vehicle: dict) -> bool:
"""Check if a vehicle is classified as electric."""
return vehicle["type"] == "electric"
48
# Main program execution
if __name__ == "__main__":
vehicles = [
{"name": "Tesla Model S", "type": "electric", "range":
600},
{"name": "Ford F-150", "type": "diesel", "range": 800},
{"name": "Nissan Leaf", "type": "electric", "range":
300},
]
Built-in operations like filter and map handle computations in a fully declarative manner.
They focus solely on the desired outcome (extracting data and computing averages) without
mutating the original list.
Characteristics
OOP fundamentally binds data and behavior (methods) together inside unified objects. It
drastically improves code reusability and system scalability while heavily enforcing the DRY
(Don't Repeat Yourself) principle.
Key Concepts
Encapsulation: The bundling of data variables and the methods that control them
into a single, secure class.
Inheritance: The ability to derive new, specialized classes from existing parent
classes, promoting logical code extension.
Polymorphism: The capability of methods to behave differently based on the specific
object that is executing them.
49
Abstraction: The practice of hiding complex implementation details internally,
exposing only the safe, necessary features to external components.
class Vehicle:
"""Base blueprint for standard vehicles."""
def __init__(self, name: str, fuel_capacity: float,
fuel_efficiency: float):
[Link] = name
self.fuel_capacity = fuel_capacity # measured in
liters
self.fuel_efficiency = fuel_efficiency # measured in
km/l
@property
def max_range(self) -> float:
"""Calculate the maximum potential range of the
vehicle."""
return self.fuel_capacity * self.fuel_efficiency
class ElectricVehicle(Vehicle):
"""Specialized electric vehicle class inheriting from the
Vehicle base."""
def __init__(self, name: str, battery_capacity: float,
efficiency: float):
# Initializing the parent class with zeroed fuel values
super().__init__(name, fuel_capacity=0,
fuel_efficiency=0)
self.battery_capacity = battery_capacity # measured in
kWh
[Link] = efficiency # measured in
km/kWh
@property
def max_range(self) -> float:
"""Override the max_range property specifically for
electric metrics."""
return self.battery_capacity * [Link]
50
# Main program execution
if __name__ == "__main__":
car = Vehicle("Toyota Corolla", 50, 15) # 50L tank, 15
km/L
ev = ElectricVehicle("Tesla Model 3", 75, 6) # 75 kWh
battery, 6 km/kWh
print(car)
print(ev)
Output:
Advantages
Modular and Scalable: Logic can be cleanly reused via inheritance and object
composition.
Encapsulation provides strict data security and access control.
Polymorphism allows dynamic flexibility across different object types.
Disadvantages
Often considered overkill and overly verbose for small, simple scripts.
Requires a solid architectural understanding of design principles to implement
correctly.
Comparison Table
Feature Procedural Functional Object-Oriented
Programming Programming Programming (OOP)
Focus Step-by-step Pure functions and data Classes and object
procedures and immutability instances
instructions
State Handled via Data is strictly Encapsulated safely
Management global/shared immutable within objects
variables
Reusability Code is reused by Function reuse and Leverages inheritance
calling global composition and polymorphism
functions
Key Concepts Functions, top- Pure functions, Encapsulation,
down flow, recursion, higher-order inheritance,
subroutines functions polymorphism,
abstraction
51
Ideal Use Case Small utility scripts, Complex data Large-scale, modular,
basic linear tasks transformations, scalable enterprise
scientific computing systems
import csv
import datetime
from task import (
Task,
RecurringTask,
) # Assumes Task and RecurringTask are defined in [Link]
class TaskCsvDAO:
"""Data Access Object for managing Task persistence via
CSV."""
52
"description",
]
task_type = row["type"]
title = row["title"]
description = row["description"]
try:
date_due = [Link](
row["date_due"], "%Y-%m-%d"
)
date_created =
[Link](
row["date_created"], "%Y-%m-%d"
)
completed = row["completed"] == "True"
except (ValueError, KeyError):
print(f"Skipping row due to invalid date
formats: {row}")
continue
completed_dates = []
53
if [Link]("completed_dates"):
completed_dates = [
[Link]([Link](), "%Y-%m-%d")
for date in
row["completed_dates"].split(",")
if [Link]()
]
if task_type == "Task":
task = Task(title, date_due, description)
[Link] = completed
task.date_created = date_created
elif task_type == "RecurringTask":
interval = [Link](
days=int(row["interval"].split()[0])
)
task = RecurringTask(title, date_due,
description, interval)
task.completed_dates = completed_dates
[Link] = completed
task.date_created = date_created
else:
print(f"Unknown task classification in
row: {row}")
continue
task_list.append(task)
except FileNotFoundError:
print("Task CSV file not found on disk. Initializing
a fresh database.")
return task_list
54
"title": [Link],
"type": (
"RecurringTask"
if isinstance(task,
RecurringTask)
else "Task"
),
"date_due": (
task.date_due.strftime("%Y-%m-
%d")
if isinstance(task.date_due,
[Link])
else task.date_due
),
"completed": [Link],
"description": [Link],
"interval": (
str([Link])
if isinstance(task,
RecurringTask)
else ""
),
"completed_dates": (
",".join(
[Link]("%Y-%m-%d")
for date in
task.completed_dates
)
if isinstance(task,
RecurringTask)
else ""
),
"date_created": (
task.date_created.strftime("%Y-
%m-%d")
if isinstance(task.date_created,
[Link])
else task.date_created
),
}
[Link](row)
except Exception as e:
print(f"Error serializing task:
{[Link]}, Details: {e}")
print("All tasks successfully serialized and
saved to CSV.")
55
except Exception as e:
print(f"Critical error accessing disk to save tasks:
{e}")
Output:
----------MENU------------
| 1. Add a new task to the list.
| 2. View the completed and uncompleted tasks in the list.
| 3. Remove a task from the list.
| 4. Select a Task to perform
(Complete Task/ description/Change due date).
| 5. Change the title of a task.
| 6. View Task Overdue.
| 7. Save and exit the program.
--------------------------
Please make a selection from (1-6): 2
56
3. Task-Swiming (created: 2024-11-19 00:00:00, due: 2024-11-25
00:00:00, Completed: False). I love to swim
4. Task-Attend OOP Lectures (created: 2024-11-20 14:40:01.793828,
due: 2024-11-22 00:00:00, Completed: False). OOP Demonstration is
on Friday!!!
----------MENU------------
| 1. Add a new task to the list.
| 2. View the completed and uncompleted tasks in the list.
| 3. Remove a task from the list.
| 4. Select a Task to perform
(Complete Task/ description/Change due date).
| 5. Change the title of a task.
| 6. View Task Overdue.
| 7. Save and exit the program.
--------------------------
Please make a selection from (1-6): 7
57
Week 7 - SOLID Principles and
Python Exceptions
As we advance in our Object-Oriented Programming (OOP) journey, we must shift our focus
from merely making code work to making it maintainable, scalable, and robust. In Week 7,
we explored the SOLID design principles and how to handle runtime anomalies using Python
Exceptions. Finally, we applied these concepts to massively refactor our To-Do List
application by properly separating user interfaces from business logic.
58
Application: This principle promotes smaller, highly specific blueprints over large,
monolithic ones. Our Task class only provides necessary methods; it doesn't force
standard tasks to implement recurring logic they don't need.
except IndexError:
# This catches out-of-bound list accesses
print("Error: That task number does not exist. Please try
again.")
except ValueError:
# This catches non-integer text inputs
print("Error: Please enter a valid numerical digit.")
59
1. The Factory Pattern (TaskFactory): A dedicated method responsible purely for
instantiating objects dynamically based on the arguments provided (e.g., creating a
RecurringTask if an interval is detected, otherwise a standard Task).
2. The Business Logic (TaskManagerController): A middleman class that handles the
core application logic (adding tasks, completing tasks, calling the DAOs) without ever
touching print() or input().
3. The Presentation Layer (CommandLineUI): A dedicated class purely for printing the
menus, capturing user keyboard inputs, and catching IndexError exceptions
smoothly.
Full Refactored Application Code
Below is the updated structure, split into appropriate modules to reflect the Separation of
Concerns.
# ==========================================
# Module: [Link]
# Contains: Task, RecurringTask, and TaskFactory
# ==========================================
import datetime
from typing import Any
class Task:
def __init__(self, title: str, date_due: [Link]):
[Link] = title
self.date_due = date_due
[Link] = False
class RecurringTask(Task):
def __init__(self, title: str, date_due: [Link],
interval: int):
super().__init__(title, date_due)
[Link] = interval
class TaskFactory:
"""Factory Pattern to cleanly handle object instantiation."""
@staticmethod
def create_task(title: str, date: [Link],
**kwargs: Any) -> Task:
# Determines task type based on the presence of the
'interval' keyword
if "interval" in kwargs:
return RecurringTask(title, date, kwargs["interval"])
return Task(title, date)
# ==========================================
60
# Module: task_list.py
# Contains: TaskList
# ==========================================
class TaskList:
def __init__(self, owner: str):
[Link] = owner
[Link] = []
# ==========================================
# Module: [Link]
# Contains: TaskManagerController
# ==========================================
class TaskManagerController:
"""Handles business logic, fully separated from UI console
prints."""
def __init__(self, owner: str):
self.task_list = TaskList(owner)
self.task_list.add_task(new_task)
61
# ==========================================
# Module: [Link]
# Contains: CommandLineUI
# ==========================================
class CommandLineUI:
"""Handles ALL user inputs and console outputs. Catches
Exceptions."""
def __init__(self, controller: TaskManagerController):
[Link] = controller
def _print_menu(self):
print("\n--- SOLID To-Do Manager ---")
print("1. Add Standard Task")
print("2. Add Recurring Task")
print("3. View Tasks")
print("4. Complete a Task")
print("5. Exit")
def run(self):
while True:
self._print_menu()
choice = input("Select an option: ")
if choice == "1":
title = input("Enter task title: ")
# Simplified date for demonstration
date = [Link]()
[Link].process_new_task(title, date)
print("Task added successfully.")
62
print(f"{idx}: {status} {[Link]}")
# ==========================================
# Module: [Link]
# Contains: Execution Entry Point
# ==========================================
if __name__ == "__main__":
# The dependencies are injected here, satisfying DIP
app_controller = TaskManagerController("Group4")
app_ui = CommandLineUI(app_controller)
63
Week 8 - Debugging, Properties,
and Persistence
As our software applications grow in complexity, the methods we use to maintain and
enhance them must also evolve. This week's laboratory exercises transition our focus from
writing initial code to maintaining and preserving it. We explored the critical process of
Debugging to systematically eliminate logical errors, utilized the @property decorator to
create dynamic object attributes, and implemented the Data Access Object (DAO) pattern
alongside CSV serialization to ensure our application's data persists between sessions.
64
Figure 5 Debugging options
class VehicleSimulator:
def __init__(self, initial_velocity: int = 0) -> None:
self.current_velocity = initial_velocity
self.total_distance = 0
self.elapsed_time = 0
65
Python Properties using the @property
Decorator
In Object-Oriented Programming, we often need to derive data based on existing attributes.
Writing a standard method requires the user to call it with parentheses (). However, Python
provides the @property decorator, which transforms a standard method so that it can be
accessed exactly like a standard attribute.
This is incredibly useful for data that needs to be computed strictly when requested. We
applied this to our To-Do List manager by creating a property that filters out completed tasks
using elegant list comprehension.
This snippet shows how the decorator is applied above the function definition, creating a
seamlessly calculated attribute.
class AdvancedTaskList:
def __init__(self, list_owner: str):
self.list_owner = list_owner
self.master_task_array = []
66
We utilized Serialization, which is the process of converting complex Python objects into a
raw byte stream or text format. Specifically, we used the Comma Separated Values (CSV)
format. To keep our code modular, we implemented a Data Access Object (DAO). The DAO
pattern isolates all file-handling logic away from the main application, ensuring the core
program doesn't need to understand how or where the data is saved.
import csv
import datetime
from typing import List, Any
class TaskPersistenceManager:
"""DAO pattern implementation for CSV file handling."""
def __init__(self, file_destination: str) -> None:
self.file_destination = file_destination
self.column_headers = ["title", "type", "date_due",
"completed"]
reconstructed_task =
TrackableTask(row_data["title"], parsed_date)
# Casting string "True"/"False" to boolean
67
reconstructed_task.is_finished =
(row_data["completed"] == "True")
extracted_tasks.append(reconstructed_task)
except FileNotFoundError:
print("Warning: Storage file missing. Booting with a
fresh database.")
return extracted_tasks
68
Week 9 - Data Structures &
Abstract Classes
In Week 9, we explored Python's core Data Structures (Lists, Dictionaries, Tuples, Sets) and
the concept of Abstract Classes.
# Standard loop
for brand in brands:
69
print(brand)
2. Dictionaries
A Dictionary stores data in key:value pairs. Keys must be unique, allowing for rapid data
retrieval. They can be created using {} or the dict() constructor.
capitals = {
"Japan": "Tokyo",
"France": "Paris",
70
"Canada": "Ottawa"
}
# Deleting
del capitals["France"]
print("Dictionary:", capitals)
print("All Keys:", [Link]())
print("All Values:", [Link]())
3. Tuples
A Tuple is similar to a list but immutable—once created, its elements cannot be changed.
They are defined using parentheses (). Tuples support destructuring, allowing you to unpack
values directly into variables.
# Destructuring
device1, device2, device3 = devices
print(device1, device2, device3)
71
4. Sets
A Set is an unordered collection of unique and immutable elements. It automatically
removes duplicates and supports mathematical operations like Union, Intersection, and
Difference.
primes = {2, 3, 5, 7}
[Link](11)
# Set Operations
set_b = {5, 7, 9, 13}
Abstract Classes
An Abstract Class acts as a strict blueprint for other classes. It cannot be instantiated on its
own. Any child class inheriting from an abstract class must implement the methods marked
with the @abstractmethod decorator.
In Python, this requires importing ABC (Abstract Base Class) and abstractmethod from the
abc module.
def standard_method(self):
print("This is a normal inherited method.")
72
# Child Classes
class Circle(Shape):
def calculate_area(self):
print("Calculating Circle Area: pi * r^2")
class Square(Shape):
def calculate_area(self):
print("Calculating Square Area: side * side")
Portfolio Exercise 6
Priority Task
class PriorityTask(Task):
def __init__(
self, title: str, date_due: datetime, priority="low",
description: str = ""
):
super().__init__(title, date_due, description,
"PriorityTask")
[Link] = priority
def __str__(self):
status = "Completed" if [Link] else "Not
Completed"
return f"{[Link]} - Priority - {[Link]}
(created: {self.date_created.strftime("%Y-%m-%d")}, status:
{status})"
73