DocsHub
Object-Oriented Programming

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 typeFirst parameterAccess toDecorator
Instance methodselfinstance + classnone
Class methodclsclass only@classmethod
Static methodnothing specialnothing 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 unaffected

Class 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 PKR

All 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))    # True

Notice — 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")) # True

Output:

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
True

When to use which

SituationUse
Working with instance data — self.balanceInstance method
Working with class data — cls.bank_nameClass method
Creating objects from different formatsClass method
Utility function related to the classStatic method
Validation logic that does not need instance dataStatic 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

TypeDecoratorFirst paramCan access
Instance methodnoneselfinstance variables + class variables
Class method@classmethodclsclass variables only
Static method@staticmethodnonenothing automatic

On this page