Object-Oriented Programming
Classes, objects, encapsulation, inheritance, polymorphism, and operator methods.
- Explain OOP vs procedural programming
- Define a class with
__init__, instance methods, and class methods - Distinguish class variables from instance variables
- Create objects (instances) and call their methods
- Explain encapsulation, inheritance, and polymorphism
- Understand that every Python value is an object of a built-in class
OOP vs Procedural Programming
Procedural: Break the task into a sequence of steps. The steps are the focus.
OOP: Identify the objects in the problem. Design centres on objects and how they interact (like a diner, waiter and chef each playing their role).
You can write OOP-style code in non-OOP languages (like C), and you can write procedural-style code in an OOP language (like Python). What makes a program OOP is the design, not the language.
OOP Language Requirements
- Encapsulation β Bundle data (state) and methods (behaviour) into one unit (a class). Hide internal details so users interact through a clean interface.
- Inheritance β A subclass (child) can inherit data members and methods from a superclass (parent) and add its own.
- Polymorphism β "Many forms." Objects of different classes can be treated as objects of a common superclass; related methods can share the same name.
Class and Object β The Blueprint Analogy
| Blueprint | Class |
|---|---|
| One blueprint, many houses | One class, many objects |
| Houses share same structure | Objects share same structure |
| Blueprint needs no physical space | Class needs no memory |
| Each house uses physical resources | Each object uses memory |
An object has state (data attributes) and behaviour (methods). A class is the template from which objects are created. An object is also called an instance of the class.
Class Definition Syntax
class ClassName:
class_variable = value # shared by ALL objects
@classmethod
def class_method(cls, ...):
cls.class_variable = ... # operates on class, not instance
def __init__(self, param1, param2):
self.attr1 = param1 # instance variable
self.attr2 = param2 # instance variable
def instance_method(self):
... # operates on this instance
__init__ β The Object Initialiser
__init__ is automatically called when you create a new object. It defines and initialises the instance variables.
class Coordinate:
def __init__(self, xc, yc):
self.x = xc # self.x is an instance variable
self.y = yc # belongs to the whole object
# xc and yc are local parameters
self is the first parameter of every instance method. It refers to the current object. (The name self is a convention β not required but always used.)
Class Variables vs Instance Variables
class Student:
uni = "MU" # CLASS variable (shared)
@classmethod
def setUni(cls, u): cls.uni = u # changes the shared copy
def __init__(self, name, age):
self.name = name # INSTANCE variable (per object)
self.age = age
def changeName(self, name): self.name = name
s1 = Student("John", 25)
s2 = Student("Mary", 23)
Student.setUni("UWA")
print(s1.uni) # UWA (both share the class variable)
print(s2.uni) # UWA
s1.changeName("Peter")
print(s1.name) # Peter
print(s2.name) # Mary (unaffected)
print(Student.uni) # UWA (class var accessible by class name)
print(Student.name) # AttributeError! name is an instance var
β οΈ Changing Class Variable via Object Reference
This is a trap! Assigning to a class variable via an object reference creates a new instance variable for that object only β it does NOT change the class variable.
s1.uni = "Curtin" # creates instance var in s1 ONLY
print(s1.uni) # Curtin (s1's own copy)
print(s2.uni) # UWA (still the class variable)
print(Student.uni) # UWA (class variable unchanged)
Rule: Change class variables only via the class name: Student.uni = "..." or via a class method.
Data Types Are Objects
In Python, every value is an object of some built-in class:
| Value | Class | Value | Class |
|---|---|---|---|
121 | int | [1,5,7] | list |
3.14 | float | (2,7,8) | tuple |
"Hello" | str | {1,3,5} | set |
True | bool | {"WA":"..."} | dict |
None | NoneType | range(10) | range |
Class Methods (@classmethod)
Use the @classmethod decorator. First parameter is cls (the class itself). Can only operate on class variables, not instance variables.
class Counter:
count = 0
@classmethod
def increment(cls):
cls.count += 1
Counter.increment()
Counter.increment()
print(Counter.count) # 2
Example β BankAccount class
class BankAccount:
interest_rate = 0.05 # class variable
def __init__(self, owner, balance=0):
self.owner = owner # instance variables
self.balance = balance
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
if amount > self.balance:
print("Insufficient funds")
else:
self.balance -= amount
def __str__(self): # operator method for print()
return f"{self.owner}: ${self.balance}"
acc1 = BankAccount("Alice", 1000)
acc2 = BankAccount("Bob")
acc1.deposit(500)
acc1.withdraw(200)
print(acc1) # Alice: $1300
print(acc2) # Bob: $0
- Know OOP vs procedural: OOP focuses on objects and their interactions.
- Explain encapsulation, inheritance, and polymorphism β definition questions are common.
- Distinguish class variable (shared, change via class name) vs instance variable (per object, created in
__init__). - Trace code with multiple objects: changing
s1.namedoes NOT changes2.name. - Know the
@classmethoddecorator and the role ofclsvsself. - Dangerous trap:
obj.class_var = valuecreates an instance variable, not changes the class variable.
- Forgetting
selfas the first parameter of instance methods β causes a TypeError when called. - Accessing
Student.name(an instance variable via class name) β gives AttributeError. - Modifying a class variable via an object (
s1.uni = "X") thinking it changes it for all β it only creates a new instance variable for s1. - Confusing
@classmethod(usecls) with regular instance method (useself).
- OOP = objects with state (data) + behaviour (methods). Procedural = sequence of steps.
- OOP languages need: Encapsulation, Inheritance, Polymorphism.
- Class = blueprint. Object = instance. One class β many objects.
__init__(self, ...)runs automatically when an object is created.- Class variable: shared by all objects β change via
ClassName.var. - Instance variable: unique per object β created with
self.varin__init__. - Every Python value is an object of a built-in class.