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

Quiz Time! (Lecture 01)

What is the process of creating an object from a class called?

A) Compilation

B) Instantiation

C) Inheritance

D) Encapsulation

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?

A) The class itself

B) The parent class

C) The current instance

D) A global variable

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?

A) No prefix

B) _single underscore

C) __double underscore

D) #hash

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) Constructor

B) Destructor

C) Getter method

D) Static method

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:

A) Base class

B) Super class

C) Derived class

D) Abstract class

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)

A) 120

B) AttributeError

C) None

D) Toyota

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()

A) Hello from A

B) Hello from B

C) Error

D) Nothing

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]
A B C start found!

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()

A) Nothing wrong

B) Should use type() instead

C) Checking types manually defeats the purpose

D) Missing else clause

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?

“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

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

Deadline: 2026-03-30

-1 point penalty if missed!

Requirements: Git & GitHub

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

Next up: Composition & Aggregation