Python Classes and Objects: The Complete Beginner's Guide
In Day 10 you learned reusable functions. Now you will learn classes and objects: how data and behavior are bundled into coherent units with \`__init__\`, \`self\`, methods, attributes, properties, and core OOP design principles.
Chapter 11 of 20 · Intermediate · 45 min · Python Programming Course
In Day 10 you learned that functions bundle related logic into a reusable unit. Classes take that idea one step further - they bundle related data and logic together into a single unit that represents a real-world concept.
A bank account has a balance, an owner, and operations like deposit and withdraw. A student has a name, scores, and behaviour like calculating an average. A product has a price, stock count, and actions like applying a discount. In each case, the data and the operations on that data naturally belong together.
Before classes, you would represent a student as a loose collection of variables and functions. With classes, you represent a student as a single object - one thing that knows its own data and knows what it can do with that data.
This is Object-Oriented Programming - the most widely used programming paradigm in professional software development.
What You Will Learn in This Chapter
By the end of this tutorial you will be able to:
- Define classes using
class - Write
__init__to initialise object attributes - Understand
selfand why it exists - Define instance methods that operate on object data
- Distinguish instance attributes from class attributes
- Write
__str__and__repr__for readable object output - Use properties to add validation to attribute access
- Understand encapsulation and why it matters
- Write multiple classes that work together
- Avoid the most common OOP mistakes beginners make
Estimated time: 45 minutes reading + 25 minutes practice
Classes vs Functions - What Changes and Why
Before writing a single line of OOP code, understand what problem it solves.
# Without classes - data and logic are separate
def calculate_average(student):
return sum(student["scores"]) / len(student["scores"])
def assign_grade(student):
avg = calculate_average(student)
if avg >= 90: return "A"
if avg >= 80: return "B"
if avg >= 70: return "C"
return "F"
def describe_student(student):
avg = calculate_average(student)
grade = assign_grade(student)
return f"{student['name']}: {avg:.1f} ({grade})"
student1 = {"name": "Alice", "scores": [88, 92, 79]}
student2 = {"name": "Bob", "scores": [72, 65, 81]}
print(describe_student(student1))
print(describe_student(student2))
# With classes - data and logic are bundled together
class Student:
def __init__(self, name, scores):
self.name = name
self.scores = scores
def calculate_average(self):
return sum(self.scores) / len(self.scores)
def assign_grade(self):
avg = self.calculate_average()
if avg >= 90: return "A"
if avg >= 80: return "B"
if avg >= 70: return "C"
return "F"
def describe(self):
avg = self.calculate_average()
grade = self.assign_grade()
return f"{self.name}: {avg:.1f} ({grade})"
Defining a Class
class BankAccount:
pass
account = BankAccount()
print(type(account))
The __init__ Method - Initialising Objects
__init__ is called automatically when you create a new instance:
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
Understanding self
self refers to the specific instance on which a method is called:
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
Why explicit self? account.deposit(500) is equivalent to BankAccount.deposit(account, 500).
Instance Attributes
Instance attributes are set on self and belong to each object independently:
class Student:
def __init__(self, name, age, scores):
self.name = name
self.age = age
self.scores = scores
Avoid adding attributes outside __init__, which makes object shape inconsistent.
Instance Methods
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
self.transactions = []
def deposit(self, amount):
if amount <= 0:
return "Deposit amount must be positive"
self.balance += amount
self.transactions.append(f"+{amount}")
return f"Deposited {amount}. New balance: {self.balance}"
def withdraw(self, amount):
if amount <= 0:
return "Withdrawal amount must be positive"
if amount > self.balance:
return f"Insufficient funds. Balance: {self.balance}"
self.balance -= amount
self.transactions.append(f"-{amount}")
return f"Withdrew {amount}. New balance: {self.balance}"
Class Attributes
Class attributes are shared across all instances:
class Student:
school_name = "Schoolabe"
student_count = 0
def __init__(self, name, scores):
self.name = name
self.scores = scores
Student.student_count += 1
Class Methods and Static Methods
class Student:
def __init__(self, name, scores):
self.name = name
self.scores = scores
@classmethod
def from_string(cls, data_string):
parts = data_string.split(",")
name = parts[0].strip()
scores = [int(s.strip()) for s in parts[1:]]
return cls(name, scores)
class MathHelper:
@staticmethod
def is_prime(n):
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
__str__ and __repr__
Implement readable output for users and developers:
class Student:
def __init__(self, name, scores):
self.name = name
self.scores = scores
def __str__(self):
avg = sum(self.scores) / len(self.scores)
return f"Student({self.name}, avg={avg:.1f})"
def __repr__(self):
return f"Student(name={self.name!r}, scores={self.scores!r})"
Properties - Controlled Attribute Access
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, amount):
if amount < 0:
raise ValueError("Balance cannot be negative")
self._balance = amount
Properties are ideal for validation, read-only access, and computed values.
Encapsulation
Encapsulation means bundling data with methods and controlling access:
class Employee:
def __init__(self, name, salary):
self.name = name
self._department = "General"
self.__salary = salary
_ is internal-use convention. __ triggers name mangling.
Special Methods
Dunder methods let objects behave like built-ins:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
A Complete Working Program
Here is a library management system where two classes work together:
class Book:
def __init__(self, title, author, isbn):
self.title = title
self.author = author
self.isbn = isbn
self._is_available = True
self._borrower = None
@property
def is_available(self):
return self._is_available
def checkout(self, member_name):
if not self._is_available:
return f"'{self.title}' is already checked out by {self._borrower}"
self._is_available = False
self._borrower = member_name
return f"'{self.title}' checked out to {member_name}"
def return_book(self):
if self._is_available:
return f"'{self.title}' was not checked out"
borrower = self._borrower
self._is_available = True
self._borrower = None
return f"'{self.title}' returned by {borrower}"
class Library:
def __init__(self, name):
self.name = name
self._books = []
def add_book(self, book):
self._books.append(book)
return f"Added: {book.title}"
def available_books(self):
return [b for b in self._books if b.is_available]
5 OOP Mistakes Every Beginner Makes
- Forgetting
selfin method definitions - Using class attributes for mutable instance data
- Modifying state inside
__str__ - Returning
selffrom mutator methods without deliberate chaining design - Making every attribute private unnecessarily
Practice: Classes and Objects Exercises
- Exercise 1: Rectangle class with
area(),perimeter(),is_square() - Exercise 2: Temperature class with validation property and computed units
- Exercise 3: User class with class-level counter
- Exercise 4: Product with
__str__and__repr__ - Exercise 5: Author + Book classes working together
-> See all Python OOP exercises with solutions
What Comes Next - Day 12: Inheritance and Polymorphism
Day 12 covers:
- Single and multiple inheritance
- The
super()function - Method overriding
- Polymorphism and interfaces
- Abstract classes
- Inheritance vs composition
-> Continue to Day 12: Inheritance and Polymorphism
Frequently Asked Questions
What is a class in Python?
A class is a blueprint that defines object data (attributes) and behavior (methods).
What is the difference between a class and an object in Python?
A class is the definition; an object is an instance created from that definition.
What is __init__ in Python?
__init__ initializes an object immediately after creation.
Why does every method need self as the first parameter?
self gives the method access to the specific object instance it is operating on.
What is the difference between instance attributes and class attributes?
Instance attributes belong to each object; class attributes are shared across all objects.
What are __str__ and __repr__ in Python?
__str__ is user-facing display text; __repr__ is developer-facing representation.
What is encapsulation in Python?
Encapsulation bundles data with methods and controls external access via conventions and properties.
What is a property in Python?
A property adds controlled getter/setter behavior while preserving attribute-style access.
Chapter navigation
- Previous: Day 10: Functions
- Next: Day 12: Inheritance and Polymorphism
- Python Quiz: Take the Python quiz
- All Python exercises: Explore Python exercises
Frequently asked questions: Classes and objects
What is a class in Python?
A class is a blueprint that defines the structure and behavior of objects. It defines attributes (data) and methods (actions) that instances can use.
What is the difference between a class and an object in Python?
A class is the definition. An object (instance) is a concrete realization of that definition with actual data values.
What is __init__ in Python?
__init__ is a special method that runs automatically when an instance is created. It initializes instance attributes.
Why does every method need self as the first parameter?
self refers to the current instance. Python passes it automatically when you call an instance method, enabling access to that object’s attributes.
What is the difference between instance attributes and class attributes?
Instance attributes belong to each object independently. Class attributes are shared across all instances of the class.
What are __str__ and __repr__ in Python?
__str__ provides a human-readable string (used by print). __repr__ provides a developer-oriented representation (used by repr and the REPL).
What is encapsulation in Python?
Encapsulation bundles data with methods and controls access through conventions (_internal, __mangled) and managed interfaces such as properties.
What is a property in Python?
A property uses @property and optional setters/getters to validate or compute attribute values while preserving normal attribute access syntax.
Related Page
Day 10: Functions
Learn about day 10: functions
Learn MoreDay 12: Inheritance and Polymorphism
Continue with day 12: inheritance and polymorphism
Learn MorePython exercises hub
165+ programs with solutions
Learn MorePython quiz
Multiple-choice checks with explanations
Learn MorePython OOP programs
Classes and objects practice programs
Learn More