Please use a larger screen
This presentation is designed for desktop or projector displays.
← Back to curriculum
|
Python Object Oriented Programming
Polymorphism & PEP 8
Course 4
.draw()
One interface, many forms
← → to navigate
Course Curriculum
Where we are in the journey
0. Introduction to the course
1. Classes vs. Objects
2. Encapsulation & Access Control
3. Inheritance
4. Polymorphism & PEP 8
5. Composition & Aggregation
6. Exception Handling & Logging
7. SOLID Principles
8. UML & Design Patterns
9. Design Patterns (2nd part)
10. Testing & TDD
11. Libraries & Frameworks
12. Recap & Consultation
Quiz Time! (Lecture 01)
What is the process of creating an object from a class called?
Creating an object from a class is called instantiation — the object is an instance of the class
Recap: Lecture 01
Classes: The Blueprint
A class defines attributes (data) and methods (behavior)
It doesn't represent a tangible item until we create an object from it
Creating an object = instantiation
class Smartphone :
def __init__ (self , brand, color):
self .brand = brand
self .color = color
phone = Smartphone ("Apple" , "Black" )
# phone is an instance of Smartphone
BLUEPRINT
attributes
brand, color
methods
__init__()
class Smartphone
Quiz Time! (Lecture 01)
What does self refer to inside a method?
self refers to the specific object instance that called the method
The self Parameter
A way for an object to refer to itself
self is the first parameter in any method defined within a class
It lets the method access the instance's attributes and other methods
When you call person1.compare_age(person2), self = person1
class Person :
def __init__ (self , name, age):
self .name = name
self .age = age
def compare_age (self , other):
if self .age > other.age:
print (f"{self.name} is older" )
person1 = Person ("Alice" , 30 )
person2 = Person ("Bob" , 25 )
person1.compare_age (person2)
# Alice is older
Quiz Time! (Lecture 02)
Which prefix makes an attribute private in Python?
Double underscore triggers name mangling , making the attribute harder to access from outside
Recap: Lecture 02
Access Control
Public
Accessible from anywhere outside the class. No underscores.
self .engine_status = "Off"
def start_engine (self ):
self .engine_status = "On"
Protected _single
Accessible within class and subclasses . Convention, not enforced.
self ._fuel_level = 50
def _check_fuel (self ):
return self ._fuel_level > 0
Private __double
Accessible within class only . Name mangling prevents outside access.
self .__owner = "John"
def __display_owner (self ):
print (self .__owner)
Quiz Time! (Lecture 02)
What is get_balance() an example of?
class BankAccount :
def __init__ (self , balance):
self ._balance = balance
def get_balance (self ):
return self ._balance
A getter provides controlled read access to a protected/private attribute
Recap: Lecture 02
Encapsulation: Getters & Setters
Encapsulation = bundling data + methods that operate on it
Getters provide controlled read access
Setters provide controlled write access with validation
Keeps internal state consistent and protected
class BankAccount :
def __init__ (self , balance):
self ._balance = balance
def get_balance (self ):
return self ._balance
def deposit (self , amount):
if amount > 0 :
self ._balance += amount
Quiz Time! (Lecture 03)
A class that inherits from another class is called a:
Also known as subclass or child class — it derives from the parent/base/super class
Recap: Lecture 03
Inheritance Terminology
Base Class (Superclass) — the class from which properties and methods are inherited. Also known as "parent" class.
Inheritance allows creating a new class that is a modified version of an existing class.
Derived Class (Subclass) — the class that inherits. It can add new properties/methods or modify existing ones. Also known as "child" class.
Animal
Parent class
Dog
Child class
Cat
Child class
Quiz Time! (Lecture 03)
What's the output?
class Vehicle :
def __init__ (self , speed):
self .speed = speed
class Car (Vehicle ):
def __init__ (self , speed, brand):
self .brand = brand
car = Car (120 , "Toyota" )
print (car.speed)
Car's __init__ overrides Vehicle's but never calls super().__init__(speed) — so speed is never set!
Recap: Lecture 03
How super() Works
Call super() to access the superclass's methods that have been overridden
class Animal :
def __init__ (self , name):
self .name = name
class Dog (Animal ):
def __init__ (self , name, breed):
super ().__init__ (name)
self .breed = breed
super() refers to the parent class
Particularly useful for __init__ to ensure parent is correctly initialized
Without it, parent attributes are never set — causing AttributeError
Always call super().__init__() when overriding __init__ in a child class!
Quiz Time! (Lecture 03)
What does this code print?
class A :
def greet (self ):
print ("Hello from A" )
class B (A ):
def greet (self ):
print ("Hello from B" )
class C (B ):
pass
obj = C ()
obj.greet ()
C has no greet(), so Python follows the MRO: C → B → finds it in B
Recap: Lecture 03
Method Resolution Order (MRO)
Python searches for methods in a specific order
Starts with the current class , then goes left to right through parent classes
If a method is found, the search stops
Use ClassName.mro() to see the full order
print (C .mro ())
# [C, B, A, object]
Quiz Time! (Bridge)
What's wrong with this approach?
class Dog :
def make_sound (self ): print ("Bark" )
class Cat :
def make_sound (self ): print ("Meow" )
animals = [Dog (), Cat ()]
for animal in animals:
if isinstance (animal, Dog ):
animal.make_sound ()
elif isinstance (animal, Cat ):
animal.make_sound ()
B) Should use type() instead
C) Checking types manually defeats the purpose
This is exactly what polymorphism solves — we can just call animal.make_sound() directly!
The 4 Characteristics of OOP
Abstraction
The most essential, impressive, stable thing of an object
Encapsulation
Behavior hides internal details
Inheritance
Behaviors are inheritable
Polymorphism
Behaviors take many forms
Today's focus: Polymorphism — how one interface serves multiple forms
Agenda for Today
Recap
Quiz questions from 01 , 02 , 03
Bridge to polymorphism
Polymorphism
Definition & types
Overloading vs overriding
Duck typing
Abstract classes
Refactoring with polymorphism
Practice + PEP 8
Live coding
PEP 8 code style
Coursework overview
Understanding Polymorphism
A fundamental OOP concept where objects of different classes can be treated as objects of a common super class
poly = many, morph = form
The ability of one interface to represent multiple forms
Same method name, different behaviors depending on the object
Enables writing flexible and extensible code
speak()
Dog
"Woof!"
Cat
"Meow!"
Duck
"Quack!"
Same method call
Different results
Why Polymorphism Matters
Real-world analogy: You press play() and different players behave differently
Same remote control , different devices
Write code that works with any subclass
Add new types without changing existing code
Reduces if/elif chains and type checking
Follows the Open/Closed Principle
▶
play()
Spotify
stream audio
YouTube
stream video
Radio
tune freq
Polymorphism You Already Use
Python's built-in functions are polymorphic!
len() on different types
len ("Hello" ) # 5
len ([1 , 2 , 3 ]) # 3
len ({"a" : 1 }) # 1
+ operator overloading
5 + 3 # 8 (addition)
"Hi" + " there" # "Hi there" (concat)
[1 ] + [2 ] # [1, 2] (merge)
print() on anything
print (42 ) # "42"
print ("text" ) # "text"
print ([1 , 2 ]) # "[1, 2]"
You've already seen this in lecture 03 — Dog and Cat both had speak() methods!
Polymorphism Visual
class Animal :
def speak (self ):
pass
class Dog (Animal ):
def speak (self ):
return "Woof!"
class Cat (Animal ):
def speak (self ):
return "Meow!"
Animal
speak()
Dog
speak() → "Woof!"
Cat
speak() → "Meow!"
Polymorphism Code
class Animal :
def speak (self ):
pass
class Dog (Animal ):
def speak (self ):
return "Woof!"
class Cat (Animal ):
def speak (self ):
return "Meow!"
animals = [Dog (), Cat (), Dog (), Cat ()]
for animal in animals:
print (animal.speak ())
Output: Woof! Meow! Woof! Meow! — same method call, different behavior!
Static vs Dynamic Polymorphism
Compile-time (Static)
Resolved at compile time
Achieved through method overloading
Determined by method signature
Common in Java, C++
Overloading
Runtime (Dynamic)
Resolved at runtime
Achieved through method overriding
Determined by object's actual type
Primary form in Python
Overriding
Method Overloading vs Overriding
Overloading
Same name , different parameters
Static polymorphism
Within the same class
Resolved at compile time
Not natively supported in Python
Overriding
Same name + same parameters
Dynamic polymorphism
In a subclass
Resolved at runtime
Core mechanism in Python
Method Overloading — Java Example
In languages like Java, you can have multiple methods with the same name but different parameters
class Calculator {
int add (int a , int b ) {
return a + b ;
}
int add (int a , int b , int c ) {
return a + b + c ;
}
double add (double a , double b ) {
return a + b ;
}
}
The compiler picks the right method based on the number and type of arguments
Method Overloading in Python
Python doesn't support traditional overloading — but we can use *args
class Calculator :
def add (self , *args):
return sum (args)
calc = Calculator ()
print (calc.add (2 , 3 )) # 5
print (calc.add (2 , 3 , 4 )) # 9
print (calc.add (1.5 , 2.5 )) # 4.0
One method handles all cases — Python's dynamic nature makes traditional overloading unnecessary
Method Overriding in Python
A subclass provides its own implementation of a parent's method
class Animal :
def speak (self ):
return "..."
class Dog (Animal ):
def speak (self ):
return "Woof!"
class Cat (Animal ):
def speak (self ):
return "Meow!"
print (Animal ().speak ()) # ...
print (Dog ().speak ()) # Woof!
print (Cat ().speak ()) # Meow!
Method Overriding Rules
1. Method Signature
Same name and parameters as superclass method
class Parent :
def greet (self ):
...
class Child (Parent ):
def greet (self ):
...
2. Access Level
Cannot be more restrictive than superclass method
# If parent has public
# method, child should
# keep it public or
# more accessible
3. super() Invocation
Overridden method can be accessed using super()
class Child (Parent ):
def greet (self ):
super ().greet ()
print ("Hi!" )
Duck Typing
“If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.”
A core Python philosophy for polymorphism
Duck Typing Principle
Focuses on what an object can do , not what it is
If the object has the method your code expects, use it — regardless of type
No need for inheritance or shared base class
Pythonic way to achieve polymorphism
class Duck :
def quack (self ):
print ("Quack!" )
class Person :
def quack (self ):
print ("I can quack too!" )
def make_it_quack (thing ):
thing .quack ()
make_it_quack (Duck ())
make_it_quack (Person ())
make_it_quack() doesn't care about the type — only that the object has a quack() method
Polymorphism + Encapsulation
class PaymentProcessor :
def pay (self , amount):
raise NotImplementedError
class CreditCardProcessor (PaymentProcessor ):
def __init__ (self , card_number):
self .__card = card_number
def pay (self , amount):
print (f"Charging ${amount} to card ending {self.__card[-4:]}" )
class PayPalProcessor (PaymentProcessor ):
def __init__ (self , email):
self .__email = email
def pay (self , amount):
print (f"Sending ${amount} via PayPal to {self.__email}" )
def checkout (processor , amount):
processor .pay (amount)
# Usage — same function, different processors
checkout (CreditCardProcessor ("4242-4242-4242-4242" ), 99.99 )
checkout (PayPalProcessor ("user@email.com" ), 49.99 )
Encapsulation hides payment details • Polymorphism allows interchangeable processors
Abstract Classes Intro
A blueprint for other classes
Cannot be instantiated directly
Contains abstract methods with no implementation
Subclasses must implement all abstract methods
Enforces a contract — "you must have these methods"
Abstract Class
method() = ???
Concrete A
method() ✓
Concrete B
method() ✓
✗ AbstractClass()
Cannot instantiate!
ABC + @abstractmethod
from abc import ABC , abstractmethod
class Shape (ABC ):
@abstractmethod
def area (self ):
pass
@abstractmethod
def perimeter (self ):
pass
class Circle (Shape ):
def __init__ (self , radius):
self .radius = radius
def area (self ):
return 3.14159 * self .radius ** 2
def perimeter (self ):
return 2 * 3.14159 * self .radius
Shape() raises TypeError — you must use a concrete subclass like Circle(5)
Refactoring: Before
Identify Conditional Statements Based on Object Type
def process_animals (animals ):
for animal in animals:
if isinstance (animal, Dog ):
print ("Woof!" )
elif isinstance (animal, Cat ):
print ("Meow!" )
elif isinstance (animal, Duck ):
print ("Quack!" )
elif isinstance (animal, Cow ):
print ("Moo!" )
Every new animal type requires modifying this function — violates the Open/Closed Principle!
Refactoring: After
Clean polymorphic version
Before
for animal in animals:
if isinstance (animal, Dog ):
print ("Woof!" )
elif isinstance (animal, Cat ):
print ("Meow!" )
elif isinstance (animal, Duck ):
print ("Quack!" )
elif isinstance (animal, Cow ):
print ("Moo!" )
After
animals = [Dog (), Cat (), Duck (), Cow ()]
for animal in animals:
animal.make_sound ()
Add new animals without touching this code!
Live Coding
Shape Calculator
Hands-on Exercise
What we'll build
ABC Shape
class Circle
class Rectangle
class Square
Concepts covered
ABC & @abstractmethod
Inheritance
Polymorphism
Method overriding
View exercise guide →
Section 3
PEP 8
Python Style Guide
What is PEP 8 ?
P ython E nhancement P roposal 8
The official style guide for Python code
Improves readability and consistency
Conventions, not strict rules — but widely adopted
Written by Guido van Rossum (Python creator) and others
“A style guide is about consistency. Consistency within a project is most important.”
Why Code Style Matters
Readability
Code is read far more often than it is written
Maintainability
Clean code is easier to debug and extend
Collaboration
Shared style = less friction in teams
Longevity
Consistent code ages better over time
“Code is read far more often than it's written.” — Guido van Rossum
Indentation & Line Length
Indentation
Use 4 spaces per level
NO TABS! Configure your editor to insert spaces.
def greet (name ):
if name:
print (f"Hello, {name}" )
else :
print ("Hello, World" )
Line Length
Maximum 79 characters
Use line continuation for long expressions.
total = (first_variable
+ second_variable
- third_variable)
Blank Lines & Imports
Blank Lines
2 blank lines before top-level functions & classes
1 blank line between methods inside a class
Use blank lines sparingly within functions
Imports
One import per line
Grouped in order: stdlib , third-party , local
Alphabetical within each group
Blank line between groups
Blank Lines & Imports Examples
Bad
import os, sys, json
import requests
import my_module
class MyClass :
def method_a (self ):
pass
def method_b (self ):
pass
def helper ():
pass
Good
import json
import os
import sys
import requests
import my_module
class MyClass :
def method_a (self ):
pass
def method_b (self ):
pass
def helper ():
pass
Naming Conventions
snake_case
Variables, functions, methods
my_variable
calculate_total()
get_user_name()
CamelCase
Classes
UserProfile
BankAccount
HttpResponse
UPPER_CASE
Constants
MAX_SIZE
PI
DATABASE_URL
Naming Conventions Code
MAX_SIZE = 100
DEFAULT_COLOR = "blue"
class UserProfile :
def __init__ (self , first_name, last_name):
self .first_name = first_name
self .last_name = last_name
def get_full_name (self ):
return f"{self.first_name} {self.last_name}"
user = UserProfile ("John" , "Doe" )
display_name = user.get_full_name ()
Whitespace in Expressions
Spaces around assignment operators : x = 5
Spaces around comparison operators : x == 5
Spaces around arithmetic operators : x = y + 1
Spaces after commas : my_list = [1, 2, 3]
No space before colon in slicing: my_list[1:3]
No space inside brackets: my_dict["key"]
Whitespace Examples
Bad
x=y+1
my_list=[1,2,3]
my_dict ['key'] = value
result = x *2+ y *3
print ( x )
foo (1)
Good
x = y + 1
my_list = [1 , 2 , 3 ]
my_dict['key' ] = value
result = x * 2 + y * 3
print (x)
foo(1 )
Type Hints
Python 3.5+ supports type annotations for better documentation and tooling
def add (x : int , y : int ) -> int :
return x + y
def greet (name : str ) -> str :
return f"Hello, {name}"
class Student :
def __init__ (self , name: str , grade: float ) -> None :
self .name: str = name
self .grade: float = grade
Type hints don't enforce types at runtime — they help IDEs , linters , and developers understand the code
PEP 8 and OOP
Class Naming
Use CamelCase
Be descriptive
class BankAccount :
...
class HttpResponseParser :
...
Method Naming
Use snake_case
Describe the action
def get_balance (self ):
...
def calculate_interest (self ):
...
Special Methods
Follow __dunder__ names
Predefined by Python
def __init__ (self ):
...
def __str__ (self ):
...
PEP 8 Tools
Linters & Formatters
flake8 Style guide checker
pylint Code analysis tool
black Automatic code formatter
isort Import sorter
IDE Integration
PyCharm — built-in PEP 8 checks
VS Code — Python extension + linters
Ruff — extremely fast linter
Configure once, follow forever!
Section 4
Course Work
Individual Project Overview
Steps to Complete
1 Select a topic
2 Write code
3 Write a report
4 Check evaluation system
5 Present and defend
Write code
Push to GitHub
Fix bugs / Add features
repeat
Write report
Select a Topic
Management Systems
Library
Movie Theatre
Hospital
Restaurant
Games & Apps
Battleship, Chess
File Manager
App Integration
DND Helper
Data & Algorithms
Finance Tracking
Birthday Reminder
Stack / Graph Class
Sort Strategies
Converter System
Topic Registration
Register through Moodle
Confirm with lecturer
Must be unique within your group
Deadline: 2026-03-30
-1 point penalty if missed!
Requirements: Git & GitHub
Upload program + report to one GitHub repository
Both must be on GitHub before the deadline
Use meaningful commit messages
Structure your project with clear folders
Your GitHub repository is the single source of truth for grading
Requirements: 4 OOP Pillars
Your code must demonstrate all four:
Polymorphism
Multiple classes with shared interface
Abstraction
Abstract base classes or simplified interfaces
Inheritance
Class hierarchies with parent-child relationships
Encapsulation
Private attributes with getters/setters
Each pillar must be described in the report with code snippets
Requirements: Design Pattern + Composition
Choose 1 design pattern to implement:
Singleton
Factory Method
Abstract Factory
Builder
Prototype
Adapter
Composite
Decorator
Must also use composition and/or aggregation . Explain in the report.
Requirements: File I/O + Testing + Code Style
File I/O
Read/write data using:
TXT files
CSV files
Database (SQLite)
Testing
Write tests using:
unittest framework
Test key functionality
Aim for coverage
Code Style
Follow PEP 8:
Naming conventions
Proper formatting
Python only
Report Structure
Markdown format • English or Lithuanian
1. Introduction
What the project is, how to run it, how to use it
2. Body / Analysis
Implementation details, OOP pillars, design patterns
3. Results
3-5 sentences on what was achieved
4. Conclusions
Outcomes, lessons learned, future improvements
Report Example
Your README.md should look something like this:
# Library Management System
## 1. Introduction
A command-line library system built with Python.
Supports adding books, borrowing, returning, and searching.
### How to run
```bash
python main.py
```
## 2. Body / Analysis
### OOP Pillars
- **Encapsulation** : Book._isbn is private, accessed via property
- **Inheritance** : EBook(Book), AudioBook(Book)
- **Polymorphism** : each type overrides .display_info()
- **Abstraction** : MediaItem ABC defines the interface
### Design Pattern
Observer pattern — notifies users when a book is available.
## 3. Results
Implemented 8 classes across 4 modules. All 15 unit tests pass.
## 4. Conclusions
Learned to apply SOLID principles in practice. Would add a GUI next.
Evaluation System
Item
Deadline
Weight
Points
Topic selection
2026-03-30
—
-1 if missed
Code (items 1-6)
2026-04-24
70%
2.1
Report (item 7)
2026-04-24
30%
0.9
Defense (oral)
2026-04-27 to 2026-05-15
—
—
Total
100%
3.0
Late penalty: -25% per week after the deadline
Summary
Key Takeaways
Polymorphism = many forms / one interface
Duck typing = focus on behavior, not type
Abstract classes enforce interface contracts
PEP 8 = clean, readable Python
Coursework : start early, register by 2026-03-30!
Next up: Composition & Aggregation
Skip Quiz ▶
☼
‹
›