Welcome to one of the most important lessons in your Python journey! By the end of this lesson, you will understand Object-Oriented Programming (OOP) · the style of writing code that is used in virtually every professional Python project in the world.
You will learn:
- What OOP is and why it exists
- What a class is and how to create one
- What an object is and how to create one from a class
- What the
__init__()method does and why it is so important - What
selfmeans and how it connects everything together
This is a big idea, but we will break it down one small step at a time. No prior knowledge of OOP is needed. Let's go!
Before diving into OOP, make sure you are comfortable with the following ideas. If any of these are new to you, read through the short explanation provided.
Variables · A variable stores a value in memory. Example: name = "Alice" stores the text "Alice" with the label name.
Functions · A function is a block of reusable code. Example:
def say_hello():
print("Hello!")Calling a function · Running a function by writing its name followed by parentheses: say_hello()
Passing arguments · Giving information to a function when you call it: greet("Alice")
If you feel confident with those ideas, you are ready to begin.
Why Does OOP Exist?
Imagine you are building a school management system. You need to keep track of hundreds of students. Each student has a name, age, grade, and scores. Without OOP, you might write something like this:
student1_name = "Amara"
student1_age = 14
student1_grade = "JSS2"
student2_name = "Emeka"
student2_age = 15
student2_grade = "JSS3"This gets messy very quickly. What if you have 500 students? You would need 1,500 separate variables. Finding, changing, or using any student's data becomes a nightmare.
OOP solves this problem by letting you create a single "template" (called a class) that describes what a student looks like, and then produce as many individual students (called objects) as you need · each with their own data stored neatly inside them.
Analogy: Think of a class as a cookie cutter and objects as the individual cookies made from that cutter. Every cookie has the same shape (from the same cutter), but each one can have different decorations (its own data).
What Does OOP Give You?
OOP provides a clear structure to programs. It makes code:
- Easier to maintain · you fix one class, and all objects benefit
- Easier to reuse · you use the same class in many places
- Easier to read · code mirrors how the real world works
OOP also follows the DRY principle · Don't Repeat Yourself. Instead of writing the same logic over and over, you write it once inside a class and reuse it.
Real-world connection: Every modern software system uses OOP. When you use a banking app, each bank account is an object. When you shop online, each product in your cart is an object. When you play a video game, each character is an object.
What is a Class?
A class is a blueprint or template. It defines what data an object will hold and what actions an object can perform.
Think of it this way:
| Class (Blueprint) | Objects (Real Things Made from It) |
|---|---|
Fruit | Apple, Banana, Mango |
Car | Toyota Camry, Honda Civic, Kia Rio |
Student | Amara, Emeka, Fatima |
BankAccount | Account #001, Account #002, Account #003 |
The class describes the category. The objects are the actual specific things.
How to Create a Class
In Python, you create a class using the keyword class, followed by the name of the class (starting with a capital letter by convention), and a colon :.
class MyClass:
x = 5Let's break this down line by line:
class· this keyword tells Python: "I am about to define a class"MyClass· this is the name we give to our class (capital letter is the convention):· the colon marks the beginning of the class bodyx = 5· this is a property (a variable that belongs to the class), with the value5
Expected Output: Nothing yet · defining a class doesn't produce output. It just creates the blueprint.
What is an Object?
An object is a specific instance created from a class. It is the real "thing" built using the blueprint.
To create an object, you write the class name followed by parentheses (), just like calling a function:
class MyClass:
x = 5
p1 = MyClass() # p1 is now an object created from MyClass
print(p1.x) # access the property x of p1Expected Output:
5Let's trace through what happened:
MyClass()· Python reads the class blueprint and creates a new objectp1 = MyClass()· we store that object in the variablep1p1.x· we access the propertyxthat belongs top1- The
.(dot) is the way we access properties and methods of an object
Thinking Prompt: What do you think would happen if you typed
print(p1.y)? There is noydefined in the class · Python would raise anAttributeError. Try it!
Creating Multiple Objects from the Same Class
One of the greatest powers of OOP is that you can create as many objects as you need from a single class. Each object is completely independent.
class MyClass:
x = 5
p1 = MyClass()
p2 = MyClass()
p3 = MyClass()
print(p1.x)
print(p2.x)
print(p3.x)Expected Output:
5
5
5All three objects have their own copy of x. Each object is independent · changing one does not change the others.
Deleting an Object
You can remove an object from memory using the del keyword:
class MyClass:
x = 5
p1 = MyClass()
print(p1.x) # works fine
del p1 # delete the object
# print(p1.x) # this would now cause an error: NameErrorExpected Output:
5After del p1, the variable p1 no longer exists. Trying to use it would raise a NameError.
The pass Statement in Classes
A class definition cannot be completely empty · Python will give you an error. But sometimes you want to create a class as a placeholder for later. Use pass to create a valid empty class:
class Person:
pass # pass means "do nothing, but this is valid"pass is Python's way of saying: "I know this block is empty. That's intentional." It prevents errors in empty code blocks.
The Problem: Every Object Looks the Same
With what we know so far, every object from MyClass has x = 5. But in real life, different students have different names, different cars have different brands. How do we give each object its own unique data when we create it?
The answer is the __init__() method.
What is __init__()?
__init__() is a special method built into Python. The name has double underscores on both sides (called dunder · "double under"). It is sometimes called the constructor.
__init__() is called automatically every time you create a new object from a class. It runs immediately at the moment of object creation.
Analogy: Think of
__init__()as the birth certificate form that gets filled out automatically the moment a new object is "born". Every time you create a newPersonobject, Python automatically runs__init__()and fills in that person's name, age, and other details.
Why Use __init__()?
Without __init__(), you would have to set properties one by one after creating the object · slow, repetitive, and easy to forget:
# Without __init__() — the hard, repetitive way
class Person:
pass
p1 = Person()
p1.name = "Tobias"
p1.age = 25
print(p1.name)
print(p1.age)Expected Output:
Tobias
25This works, but imagine doing this for 100 students. And what if you forget to set the age for one of them? Errors everywhere.
With __init__(), you set everything in one clean step:
# With __init__() — the clean, professional way
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person("Emil", 36)
print(p1.name)
print(p1.age)Expected Output:
Emil
36Breaking Down the __init__() Code Line by Line
class Person:
def __init__(self, name, age):
self.name = name
self.age = agedef __init__(self, name, age):· defines the__init__method. It takes three parameters:self(explained in the next section),name, andageself.name = name· stores the value passed asnameinto this object's own property callednameself.age = age· stores the value passed asageinto this object's own property calledage
p1 = Person("Emil", 36)Person("Emil", 36)· creates a newPersonobject and automatically calls__init__()withname = "Emil"andage = 36p1· the new object is stored inp1
print(p1.name) # "Emil"
print(p1.age) # 36p1.name goes to p1's personal storage and retrieves the name we gave it.
Thinking Prompt: What happens if you write
p1 = Person("Emil")· leaving out the age? Python will give aTypeErrorbecause__init__()expects bothnameandage.
Creating Multiple Objects with Different Data
Now the power becomes clear:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person("Amara", 14)
p2 = Person("Emeka", 15)
p3 = Person("Fatima", 13)
print(p1.name, p1.age)
print(p2.name, p2.age)
print(p3.name, p3.age)Expected Output:
Amara 14
Emeka 15
Fatima 13Three students, each with their own individual data, all from the same class. No repetitive variable names. Clean, organised, professional.
Default Values in __init__()
You can give parameters default values so that if the caller doesn't provide one, it falls back to the default:
class Person:
def __init__(self, name, age=18): # age defaults to 18
self.name = name
self.age = age
p1 = Person("Emil") # age not given, uses default 18
p2 = Person("Tobias", 25) # age given, uses 25
print(p1.name, p1.age)
print(p2.name, p2.age)Expected Output:
Emil 18
Tobias 25p1didn't get an age, so it used the default18p2got25, which overrode the default
Thinking Prompt: What would happen if you switched the order and put
def __init__(self, age=18, name):? Python would raise aSyntaxError· parameters with defaults must always come after parameters without defaults.
Multiple Parameters in __init__()
__init__() can accept as many parameters as you need:
class Person:
def __init__(self, name, age, city, country):
self.name = name
self.age = age
self.city = city
self.country = country
p1 = Person("Linus", 30, "Lagos", "Nigeria")
print(p1.name)
print(p1.age)
print(p1.city)
print(p1.country)Expected Output:
Linus
30
Lagos
NigeriaEach piece of data is stored inside the object and accessible through the dot notation (p1.city, etc.).
__init__() Method. Open your editor, type the examples above by hand, modify them, and observe what changes.What is self?
self is the first parameter you see in almost every method inside a class. It is a reference to the current object · the specific instance that the method is being called on.
In plain English: self means "this particular object right now".
Analogy: Imagine a class is a job description for an employee. The job description says "The employee will introduce themselves by name." But when an actual employee (an object) follows that description,
selfis that specific employee. When Amara introduces herself,selfis Amara. When Emeka introduces himself,selfis Emeka.
Why Does self Exist?
Python needs self to know which object's data to use when a method is called. Without self, a method inside a class would have no way to access the properties of a specific object.
Here's the point illustrated clearly:
class Person:
def __init__(self, name):
self.name = name # store name in THIS object
def printname(self):
print(self.name) # print THIS object's name
p1 = Person("Tobias")
p2 = Person("Linus")
p1.printname() # self refers to p1 here
p2.printname() # self refers to p2 hereExpected Output:
Tobias
LinusWhen you call p1.printname(), Python automatically passes p1 as self. So self.name becomes p1.name which is "Tobias". When you call p2.printname(), self becomes p2, so self.name is "Linus".
This is how Python knows which object's data to work with!
self Must Always Be First
self must always be the first parameter of any method defined inside a class. You cannot skip it or move it elsewhere.
class Person:
def __init__(self, name, age): # self is first
self.name = name
self.age = age
def greet(self): # self is first here too
print("Hello, my name is " + self.name)
p1 = Person("Emil", 25)
p1.greet()Expected Output:
Hello, my name is EmilNotice that when you call p1.greet(), you do NOT pass self yourself. Python fills it in automatically. But when you define the method, self must be there.
Using self to Access Properties
self lets you reach into the object and retrieve or modify any property:
class Car:
def __init__(self, brand, model, year):
self.brand = brand
self.model = model
self.year = year
def display_info(self):
print(f"{self.year} {self.brand} {self.model}")
car1 = Car("Toyota", "Corolla", 2020)
car1.display_info()Expected Output:
2020 Toyota CorollaInside display_info, self.year, self.brand, and self.model all refer to the specific car that called the method.
Using self to Call Other Methods
You can use self to call one method from inside another method of the same class:
class Person:
def __init__(self, name):
self.name = name
def greet(self):
return "Hello, " + self.name
def welcome(self):
message = self.greet() # calling greet() from welcome()
print(message + "! Welcome to our website.")
p1 = Person("Tobias")
p1.welcome()Expected Output:
Hello, Tobias! Welcome to our website.self.greet() calls the greet method on the same object (p1), which returns the greeting string, which is then used in welcome.
Can self Be Named Something Else?
Technically, yes · self is just a convention. Python does not enforce the name. You could use any valid variable name:
class Person:
def __init__(myobject, name, age): # using "myobject" instead
myobject.name = name
myobject.age = age
def greet(abc): # using "abc" instead
print("Hello, my name is " + abc.name)
p1 = Person("Emil", 36)
p1.greet()Expected Output:
Hello, my name is EmilThis works exactly the same. BUT · never actually do this in real code. Using self is a universal Python convention. Every Python programmer expects to see self. Using something else makes your code confusing and harder for others (and your future self) to read. Always use self.
self Parameter. Open your editor, type the examples above by hand, modify them, and observe what changes.Exercise 1 · Warm-Up: Build a Simple Dog Class
Objective: Practice creating a class and objects with __init__().
Scenario: You are building a pet registration system. Create a Dog class.
Steps:
- Create a class called
Dog - Add
__init__()that acceptsnameandbreed - Store both values using
self - Create two Dog objects with different names and breeds
- Print both dogs' names and breeds
Starter code:
class Dog:
def __init__(self, name, breed):
# your code here
dog1 = Dog("Rex", "German Shepherd")
dog2 = Dog("Bella", "Labrador")
# print dog1's name and breed
# print dog2's name and breedExpected Output:
Rex German Shepherd
Bella LabradorSolution:
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
dog1 = Dog("Rex", "German Shepherd")
dog2 = Dog("Bella", "Labrador")
print(dog1.name, dog1.breed)
print(dog2.name, dog2.breed)Self-check Questions:
- What would happen if you forgot
selfin__init__? - What does
dog1.namemean · what is the.doing?
Exercise 2 · Adding a Method with self
Objective: Practice using self inside a method to access properties.
Scenario: Extend the Dog class with a method that makes the dog introduce itself.
Steps:
- Keep the
Dogclass from Exercise 1 - Add a method called
introduce()that prints:"Woof! My name is [name] and I am a [breed]." - Create two Dog objects and call
introduce()on both
Solution:
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def introduce(self):
print("Woof! My name is " + self.name + " and I am a " + self.breed + ".")
dog1 = Dog("Rex", "German Shepherd")
dog2 = Dog("Bella", "Labrador")
dog1.introduce()
dog2.introduce()Expected Output:
Woof! My name is Rex and I am a German Shepherd.
Woof! My name is Bella and I am a Labrador.What-if challenge: What if you add age to the class and print it in the introduce() method too?
Exercise 3 · Student Grade Tracker
Objective: Apply all concepts together in a realistic scenario.
Scenario: You are building a student result system for a school.
Requirements:
- A
Studentclass withname,subject, andscoreproperties - A method called
result()that prints:"[name] scored [score] in [subject]." - A method called
grade()that prints:"Grade: A"if score >= 70,"Grade: B"if score >= 50, otherwise"Grade: F"
Solution:
class Student:
def __init__(self, name, subject, score):
self.name = name
self.subject = subject
self.score = score
def result(self):
print(self.name + " scored " + str(self.score) + " in " + self.subject + ".")
def grade(self):
if self.score >= 70:
print("Grade: A")
elif self.score >= 50:
print("Grade: B")
else:
print("Grade: F")
s1 = Student("Amara", "Mathematics", 85)
s2 = Student("Emeka", "English", 55)
s3 = Student("Fatima", "Science", 42)
s1.result()
s1.grade()
s2.result()
s2.grade()
s3.result()
s3.grade()Expected Output:
Amara scored 85 in Mathematics.
Grade: A
Emeka scored 55 in English.
Grade: B
Fatima scored 42 in Science.
Grade: FSelf-check Questions:
- Why do we write
str(self.score)instead of justself.scorein theresult()method? - What does
self.score >= 70compare? Whose score is it referring to?
Mistake 1 · Forgetting self in a Method Definition
# WRONG
class Dog:
def __init__(name, breed): # missing self!
self.name = name
# RIGHT
class Dog:
def __init__(self, name, breed):
self.name = namePython will raise a TypeError because without self, Python doesn't know which object to assign the name to.
Mistake 2 · Passing self When Calling a Method
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
print(self.name + " says: Woof!")
dog1 = Dog("Rex")
dog1.bark(dog1) # WRONG — passing self manually
dog1.bark() # RIGHT — Python handles self automaticallyPython fills in self automatically when you call a method on an object. Never pass it yourself.
Mistake 3 · Forgetting to Use self. When Accessing Properties
# WRONG
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
print(name + " says: Woof!") # 'name' doesn't exist here!
# RIGHT
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
print(self.name + " says: Woof!") # self.name is correctInside a method, you must use self.name to reach the object's property. Just name by itself is a completely different (non-existent) variable inside the method's local scope.
Mistake 4 · Leaving a Class Body Completely Empty
# WRONG
class Dog:
# no code here — syntax error!
# RIGHT
class Dog:
pass # use pass as a placeholderPython requires at least something in a class body. pass is the correct placeholder.
Mistake 5 · Confusing the Class with the Object
class Dog:
def __init__(self, name):
self.name = name
# WRONG — accessing property on the class, not an object
print(Dog.name) # AttributeError!
# RIGHT — create an object first
dog1 = Dog("Rex")
print(dog1.name) # RexThe class (Dog) is the blueprint. You must create an object (dog1) to have actual data.
Mistake 6 · Using a Different Name for self Without Understanding It
# This works but is confusing for everyone
class Dog:
def __init__(x, name):
x.name = nameThis technically runs, but it confuses other programmers and your future self. Always use self.
OOP with classes is used everywhere in the real world:
Banking systems · Each bank account is a BankAccount object with properties like account_number, balance, and methods like deposit() and withdraw().
E-commerce · Each product is a Product object. Each customer is a Customer object. Each order is an Order object.
Games · Each game character is a Character object with properties like name, health, level, and methods like attack() and defend().
School management · Each student, teacher, and course is its own object · exactly like the project you just built.
Data science · Libraries like Pandas and NumPy are built entirely using classes. The DataFrame you create in Pandas is an object.
Now let's combine everything into a realistic mini-project.
Project Goal: Build a Student Report Card System that manages multiple students, tracks multiple subjects, and generates a report.
Stage 1 · Setup: Define the Student Class
class Student:
def __init__(self, name, student_id):
self.name = name
self.student_id = student_id
self.scores = {} # dictionary to store subject → score pairs
def add_score(self, subject, score):
self.scores[subject] = score
def display_info(self):
print("Student Name: " + self.name)
print("Student ID: " + self.student_id)Lesson 20 complete! 🎉
You covered:
- ✅ Lesson Introduction
- ✅ Prerequisite Concepts
- ✅ Part 1: What is Object-Oriented Programming (OOP)?
- ✅ Part 2: Classes and Objects
- ✅ Part 3: The `__init__()` Method
- ✅ Part 4: The `self` Parameter
- ✅ Guided Practice Exercises
- ✅ Common Beginner Mistakes