Methods
Learn the three types of methods in Python classes — instance methods, class methods, and static methods.
Methods
A method is a function defined inside a class. Python has three types of methods, each with a different purpose and a different relationship to the class and its instances.
| Method type | First parameter | Access to | Decorator |
|---|---|---|---|
| Instance method | self | instance + class | none |
| Class method | cls | class only | @classmethod |
| Static method | nothing special | nothing automatic | @staticmethod |
We will build all three into the same BankAccount class so you can see exactly how they differ.
Instance methods
An instance method is the most common type. It takes self as the first parameter — giving it access to the specific object it is called on.
Every method you have seen so far has been an instance method. They can read and modify instance variables and class variables:
class BankAccount:
bank_name = "HBL Bank"
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
def deposit(self, amount):
if amount > 0:
self.balance += amount
print(f"{amount:,} PKR deposited. New balance: {self.balance:,} PKR")
else:
print("Deposit amount must be positive.")
def withdraw(self, amount):
if amount <= 0:
print("Withdrawal amount must be positive.")
elif amount > self.balance:
print("Insufficient balance.")
else:
self.balance -= amount
print(f"{amount:,} PKR withdrawn. Remaining balance: {self.balance:,} PKR")
def check(self):
print(f"Bank: {BankAccount.bank_name}")
print(f"Owner: {self.owner}")
print(f"Balance: {self.balance:,} PKR")
print("-" * 35)Calling instance methods:
account = BankAccount("Ahmad", 10000)
account.check()
account.deposit(5000)
account.withdraw(3000)
account.check()Output:
Bank: HBL Bank
Owner: Ahmad
Balance: 10,000 PKR
-----------------------------------
5,000 PKR deposited. New balance: 15,000 PKR
3,000 PKR withdrawn. Remaining balance: 12,000 PKR
Bank: HBL Bank
Owner: Ahmad
Balance: 12,000 PKR
-----------------------------------The key thing — self.balance is different for every account. The method works on whichever object it is called on.
account1 = BankAccount("Ahmad", 10000)
account2 = BankAccount("Sara", 5000)
account1.deposit(2000) # only changes account1's balance
account2.check() # account2 is unaffectedClass methods
A class method works on the class itself, not on a specific instance. It takes cls (the class) as its first parameter instead of self. You define it with @classmethod.
Use a class method when the action affects the class as a whole — not just one object.
The most practical example — changing the bank name for all accounts at once:
class BankAccount:
bank_name = "HBL Bank"
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
def check(self):
print(f"Bank: {BankAccount.bank_name}")
print(f"Owner: {self.owner}")
print(f"Balance: {self.balance:,} PKR")
print("-" * 35)
@classmethod
def change_bank_name(cls, new_name):
cls.bank_name = new_name
print(f"Bank name updated to: {cls.bank_name}")
@classmethod
def get_bank_info(cls):
print(f"Bank name: {cls.bank_name}")account1 = BankAccount("Ahmad", 10000)
account2 = BankAccount("Sara", 5000)
account1.check()
account2.check()
# change the bank name — affects all accounts
BankAccount.change_bank_name("MCB Bank")
account1.check()
account2.check()Output:
Bank: HBL Bank
Owner: Ahmad
Balance: 10,000 PKR
-----------------------------------
Bank: HBL Bank
Owner: Sara
Balance: 5,000 PKR
-----------------------------------
Bank name updated to: MCB Bank
Bank: MCB Bank
Owner: Ahmad
Balance: 10,000 PKR
-----------------------------------
Bank: MCB Bank
Owner: Sara
Balance: 5,000 PKR
-----------------------------------Both accounts reflect the new bank name — because bank_name is a class variable shared by all.
Class methods as alternative constructors
Another very common use of @classmethod — creating objects from different data formats. This is called an alternative constructor:
class BankAccount:
bank_name = "HBL Bank"
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
@classmethod
def from_string(cls, account_string):
"""
Create an account from a string like 'Ahmad-10000'
"""
owner, balance = account_string.split("-")
return cls(owner, int(balance))
@classmethod
def zero_balance(cls, owner):
"""
Create an account with zero balance.
"""
return cls(owner, 0)
def check(self):
print(f"Owner: {self.owner} | Balance: {self.balance:,} PKR")
# normal way
account1 = BankAccount("Ahmad", 10000)
# from a string — useful when reading from a file or database
account2 = BankAccount.from_string("Sara-25000")
# zero balance account
account3 = BankAccount.zero_balance("Omar")
account1.check() # Owner: Ahmad | Balance: 10,000 PKR
account2.check() # Owner: Sara | Balance: 25,000 PKR
account3.check() # Owner: Omar | Balance: 0 PKRAll three create a BankAccount object — just from different input formats.
cls inside a class method refers to the class itself — just like self refers to the instance. Using cls(owner, balance) is the same as calling BankAccount(owner, balance) — but using cls is better because if someone subclasses BankAccount, the method will create the right subclass instead of always creating a BankAccount.
Static methods
A static method is a regular function that lives inside a class for organizational purposes. It has no self and no cls — it does not automatically receive any information about the class or its instances. You define it with @staticmethod.
Use it for utility functions that are related to the class but do not need access to any instance or class data:
class BankAccount:
bank_name = "HBL Bank"
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
def deposit(self, amount):
if BankAccount.validate_amount(amount):
self.balance += amount
print(f"{amount:,} PKR deposited. New balance: {self.balance:,} PKR")
else:
print("Invalid amount.")
def withdraw(self, amount):
if not BankAccount.validate_amount(amount):
print("Invalid amount.")
elif amount > self.balance:
print("Insufficient balance.")
else:
self.balance -= amount
print(f"{amount:,} PKR withdrawn. Remaining balance: {self.balance:,} PKR")
@staticmethod
def validate_amount(amount):
"""Check if an amount is valid — positive number."""
return isinstance(amount, (int, float)) and amount > 0
@staticmethod
def convert_to_usd(pkr_amount, rate=280):
"""Convert PKR to USD at the given rate."""
return round(pkr_amount / rate, 2)
@staticmethod
def is_valid_owner_name(name):
"""Check if the owner name contains only letters and spaces."""
return isinstance(name, str) and name.replace(" ", "").isalpha()# static methods can be called on the class directly
print(BankAccount.validate_amount(5000)) # True
print(BankAccount.validate_amount(-100)) # False
print(BankAccount.validate_amount(0)) # False
print(BankAccount.convert_to_usd(280000)) # 1000.0
print(BankAccount.convert_to_usd(56000)) # 200.0
print(BankAccount.is_valid_owner_name("Ahmad Ali")) # True
print(BankAccount.is_valid_owner_name("Ahmad123")) # False
# or on an instance — both work
account = BankAccount("Ahmad", 10000)
print(account.validate_amount(5000)) # TrueNotice — validate_amount does not touch self.balance or cls.bank_name. It just validates a number. It belongs in BankAccount because it is conceptually related — but it does not need any account data to do its job.
All three together — the complete BankAccount
class BankAccount:
bank_name = "HBL Bank"
total_accounts = 0
def __init__(self, owner, balance):
if not BankAccount.is_valid_owner_name(owner):
raise ValueError(f"Invalid owner name: {owner}")
if not BankAccount.validate_amount(balance):
raise ValueError("Opening balance must be positive.")
self.owner = owner
self.balance = balance
BankAccount.total_accounts += 1
# --- Instance methods ---
def deposit(self, amount):
if BankAccount.validate_amount(amount):
self.balance += amount
print(f"Deposited {amount:,} PKR → Balance: {self.balance:,} PKR")
else:
print("Invalid deposit amount.")
def withdraw(self, amount):
if not BankAccount.validate_amount(amount):
print("Invalid withdrawal amount.")
elif amount > self.balance:
print(f"Insufficient balance. Available: {self.balance:,} PKR")
else:
self.balance -= amount
print(f"Withdrawn {amount:,} PKR → Balance: {self.balance:,} PKR")
def check(self):
print(f"Bank: {BankAccount.bank_name}")
print(f"Owner: {self.owner}")
print(f"Balance: {self.balance:,} PKR")
print(f" ({BankAccount.convert_to_usd(self.balance)} USD)")
print("-" * 40)
# --- Class methods ---
@classmethod
def change_bank_name(cls, new_name):
cls.bank_name = new_name
print(f"Bank name changed to: {cls.bank_name}")
@classmethod
def from_string(cls, account_string):
owner, balance = account_string.split("-")
return cls(owner, int(balance))
@classmethod
def get_total_accounts(cls):
print(f"Total accounts: {cls.total_accounts}")
# --- Static methods ---
@staticmethod
def validate_amount(amount):
return isinstance(amount, (int, float)) and amount > 0
@staticmethod
def convert_to_usd(pkr_amount, rate=280):
return round(pkr_amount / rate, 2)
@staticmethod
def is_valid_owner_name(name):
return isinstance(name, str) and name.replace(" ", "").isalpha()Using it all together:
# create accounts different ways
account1 = BankAccount("Ahmad", 10000)
account2 = BankAccount("Sara", 25000)
account3 = BankAccount.from_string("Omar-8000")
# instance methods
account1.deposit(5000)
account1.withdraw(2000)
account1.check()
account2.check()
# class method — affects all accounts
BankAccount.change_bank_name("MCB Bank")
account1.check()
account2.check()
# class method — class-level info
BankAccount.get_total_accounts()
# static methods — utility, no instance needed
print(BankAccount.validate_amount(500)) # True
print(BankAccount.convert_to_usd(28000)) # 100.0
print(BankAccount.is_valid_owner_name("Ali")) # TrueOutput:
Deposited 5,000 PKR → Balance: 15,000 PKR
Withdrawn 2,000 PKR → Balance: 13,000 PKR
Bank: HBL Bank
Owner: Ahmad
Balance: 13,000 PKR
(46.43 USD)
----------------------------------------
Bank: HBL Bank
Owner: Sara
Balance: 25,000 PKR
(89.29 USD)
----------------------------------------
Bank name changed to: MCB Bank
Bank: MCB Bank
Owner: Ahmad
Balance: 13,000 PKR
(46.43 USD)
----------------------------------------
Bank: MCB Bank
Owner: Sara
Balance: 25,000 PKR
(89.29 USD)
----------------------------------------
Total accounts: 3
True
100.0
TrueWhen to use which
| Situation | Use |
|---|---|
Working with instance data — self.balance | Instance method |
Working with class data — cls.bank_name | Class method |
| Creating objects from different formats | Class method |
| Utility function related to the class | Static method |
| Validation logic that does not need instance data | Static method |
A simple rule — if you need self, use an instance method. If you need cls but not self, use a class method. If you need neither, use a static method. When in doubt, start with an instance method.
Summary
| Type | Decorator | First param | Can access |
|---|---|---|---|
| Instance method | none | self | instance variables + class variables |
| Class method | @classmethod | cls | class variables only |
| Static method | @staticmethod | none | nothing automatic |