Classes & Objects
So far you have stored data in variables, lists and dictionaries, and written functions that work on that data. A class puts the two together: it describes a kind of thing, the data it holds and the actions it can perform. Each actual thing made from a class is called an object.
Classes are the heart of object-oriented programming (OOP). They help you model real things, such as bank accounts, students or train tickets, in code that is easy to read and reuse. Everything in Python is already an object: a string is an object of class str, and a list is an object of class list.
Defining a class and creating objects
Think of a class as a blueprint and objects as the houses built from it. You write the blueprint once with the class keyword. Class names use CapWords style: BankAccount, CricketPlayer.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
priya = BankAccount("Priya", 5000)
rohan = BankAccount("Rohan")
print(priya.owner, priya.balance)
print(rohan.owner, rohan.balance)
print(type(priya))Priya 5000 Rohan 0 <class '__main__.BankAccount'>
Calling the class like a function, BankAccount("Priya", 5000), creates a new object. Python then runs the special method __init__ (short for initialise) to set the object up. The values stored on the object, owner and balance, are called attributes. You read them with a dot: priya.balance.
What is self?
self is the object being worked on. When you write BankAccount("Priya", 5000), Python creates an empty object and passes it to __init__ as self, followed by your arguments. So self.owner = owner means "store the owner on this object". Every method receives the object as its first parameter, and by convention it is always named self.
Methods: functions that belong to a class
A method is a function defined inside a class. It describes something the object can do, and it uses self to read and change the object's attributes.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
return self.balance
def withdraw(self, amount):
if amount > self.balance:
raise ValueError(f"{self.owner} has only ₹{self.balance}")
self.balance -= amount
return self.balance
def describe(self):
return f"{self.owner}: ₹{self.balance:,}"
priya = BankAccount("Priya", 5000)
priya.deposit(2500)
priya.withdraw(1200)
print(priya.describe())
try:
priya.withdraw(10000)
except ValueError as error:
print("Error:", error)Priya: ₹6,300 Error: Priya has only ₹6300
When you call priya.deposit(2500), Python turns it into BankAccount.deposit(priya, 2500) behind the scenes. That is how self gets its value. Each object keeps its own attributes, so depositing into Priya's account never changes Rohan's.
Class attributes and instance attributes
Attributes set on self are instance attributes: each object has its own. Attributes written directly inside the class body are class attributes: one value shared by all objects of the class. They suit constants such as an interest rate or a bank name.
class SavingsAccount:
bank = "Bharat Co-operative Bank"
interest_rate = 3.5 # percent per year, shared by all accounts
accounts_opened = 0
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
SavingsAccount.accounts_opened += 1
def yearly_interest(self):
return round(self.balance * self.interest_rate / 100, 2)
a = SavingsAccount("Meera", 20000)
b = SavingsAccount("Kabir", 45000)
print(a.bank)
print(a.yearly_interest(), b.yearly_interest())
print("Accounts:", SavingsAccount.accounts_opened)Bharat Co-operative Bank 700.0 1575.0 Accounts: 2
When Python looks up self.interest_rate, it checks the object first and then the class, so every account finds the shared rate. The counter is updated through the class name, SavingsAccount.accounts_opened, so all objects share one count.
Properties: attributes with rules
Sometimes an attribute should be calculated, or protected from bad values. The @property decorator turns a method into something you read like an attribute, without brackets. A matching .setter runs your checks whenever someone assigns a new value.
class Student:
def __init__(self, name, marks):
self.name = name
self.marks = marks # goes through the setter below
@property
def marks(self):
return self._marks
@marks.setter
def marks(self, value):
if not 0 <= value <= 100:
raise ValueError("Marks must be between 0 and 100")
self._marks = value
@property
def passed(self):
return self.marks >= 40
isha = Student("Isha", 72)
print(isha.marks, isha.passed)
try:
isha.marks = 140
except ValueError as error:
print("Error:", error)72 True Error: Marks must be between 0 and 100
The real value lives in self._marks. A single leading underscore is a Python convention meaning "internal, please do not touch from outside". Python does not enforce it, but other programmers respect it.
Class methods and static methods
A normal method works on one object. A class method, marked with @classmethod, receives the class itself as cls. It is often used as an alternative way to create objects, for example from a line of text. A static method, marked with @staticmethod, receives neither; it is a plain helper function that lives inside the class because it belongs there logically.
class Train:
def __init__(self, number, name, source):
self.number = number
self.name = name
self.source = source
@classmethod
def from_text(cls, line):
number, name, source = line.split("|")
return cls(int(number), name, source)
@staticmethod
def is_valid_number(number):
return 10000 <= number <= 99999
t = Train.from_text("12015|Ajmer Shatabdi|New Delhi")
print(t.name, "from", t.source)
print(Train.is_valid_number(t.number))Ajmer Shatabdi from New Delhi True
Printing an object directly, as in print(t), shows something unhelpful like <__main__.Train object at 0x...>. You will fix that with __str__ and __repr__ in Special (Dunder) Methods.
Common mistakes
- Forgetting
selfin a method definition.def deposit(amount):fails when called withTypeError: BankAccount.deposit() takes 1 positional argument but 2 were given, because Python always passes the object first. - Forgetting
self.inside a method. Writingbalance += amountcreates or changes a local variable, not the attribute. Useself.balance. - Using a list as a class attribute for per-object data. A class-level
items = []is shared by every object. Create lists in__init__withself.items = []. - Calling a method without brackets.
priya.describegives the method itself;priya.describe()runs it.
Key points
- A class is a blueprint; objects (instances) are created by calling the class.
__init__sets up a new object, andselfrefers to the object being worked on.- Methods are functions inside a class that receive
selffirst. - Instance attributes belong to one object; class attributes are shared by all objects of the class.
@propertylets you compute or validate attributes; a leading underscore marks internal attributes.@classmethodreceives the class (cls) and is handy for alternative constructors;@staticmethodreceives neither.
Exercise
Create a class CricketPlayer with attributes name, runs (default 0) and balls (default 0). Add a method add_innings(runs, balls) that adds to both totals, and a property strike_rate that returns runs per 100 balls rounded to 2 places (return 0 if no balls were faced). Create a player Rohan, add innings of 45 off 30 balls and 82 off 61 balls, then print his totals and strike rate.
Show solution
class CricketPlayer:
def __init__(self, name, runs=0, balls=0):
self.name = name
self.runs = runs
self.balls = balls
def add_innings(self, runs, balls):
self.runs += runs
self.balls += balls
@property
def strike_rate(self):
if self.balls == 0:
return 0
return round(self.runs / self.balls * 100, 2)
rohan = CricketPlayer("Rohan")
rohan.add_innings(45, 30)
rohan.add_innings(82, 61)
print(rohan.name, rohan.runs, rohan.balls, rohan.strike_rate)Rohan 127 91 139.56
strike_rate is calculated from the current totals every time you read it, so it can never be out of date. Because it is a property, you read it without brackets.
Quick Quiz
1. What is self inside a method?
Python passes the object as the first argument, and by convention it is named self.
2. When does __init__ run?
Creating an object, such as BankAccount("Priya"), calls __init__ to set it up.
3. A class has rate = 3.5 written in its body. What is rate?
Attributes defined in the class body belong to the class and are shared.
4. What does @classmethod give a method as its first parameter?
A class method receives the class, so it can create new objects with cls(...).
Learn Python with a mentor
Free lessons get you started. Our Python Programming training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
