In Python, a method is simply a function defined inside a class.
👉 But conceptually:
✔ A method belongs to an object
✔ A method operates on object/class data
✔ Python automatically passes context (self / cls)
A method is just:
Function + Object Binding
Python treats methods as descriptors.
Meaning:
👉 Functions become methods when accessed through instances.
def greet(name):
return f"Hello, {name}!"
print(greet("Alice"))Output:
Hello, Alice!
class Person:
def greet(self):
return "Hello!"
p = Person()
print(p.greet())Output:
Hello!
Python internally converts:
p.greet()Into:
Person.greet(p)✔ Instance automatically passed
✔ self = calling object
self represents:
👉 The current instance calling the method.
Without self → Python cannot know WHICH object.
Python has three types of methods:
- Instance Methods
- Class Methods
- Static Methods
✔ Bound to instance
✔ Access instance data
✔ First parameter → self
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
print(f"{self.name} says Woof!")
d = Dog("Buddy")
d.bark()Output:
Buddy says Woof!
✔ Each object has its own data
✔ Method works on object state
class Dog:
def bark(): # ❌ ERROR
print("Woof")Error:
TypeError: bark() takes 0 positional arguments but 1 was given
✔ Python ALWAYS passes instance
✔ Bound to class
✔ Access class data
✔ First parameter → cls
✔ Uses @classmethod
class Dog:
total_dogs = 0
def __init__(self, name):
self.name = name
Dog.total_dogs += 1
@classmethod
def get_total_dogs(cls):
return cls.total_dogs
d1 = Dog("Buddy")
d2 = Dog("Max")
print(Dog.get_total_dogs())Output:
2
✔ Works with shared state
✔ No instance required
cls is NOT magic.
It simply refers to:
👉 The class calling the method.
Works beautifully with inheritance 🔥
class Animal:
species = "Animal"
@classmethod
def get_species(cls):
return cls.species
class Dog(Animal):
species = "Dog"
print(Dog.get_species())Output:
Dog
✔ cls respects child class ✅🔥
✔ No instance
✔ No class
✔ Utility logic
✔ Uses @staticmethod
class MathUtils:
@staticmethod
def add(a, b):
return a + b
print(MathUtils.add(10, 20))Output:
30
Static methods are:
Namespaced Functions
✔ Organization only
✔ No binding
class Test:
x = 10
@staticmethod
def show():
print(self.x) # ❌ ERROR✔ No self available
When accessing:
obj.methodPython returns:
👉 Bound Method Object
class Demo:
def greet(self):
print("Hello")
d = Demo()
print(d.greet)Output:
<bound method Demo.greet of <__main__.Demo object>>
✔ Function wrapped with instance
| Access Pattern | Result |
|---|---|
Class.method |
Function (unbound) |
obj.method |
Bound Method |
class Demo:
def greet(self):
print("Hello")
print(Demo.greet) # Function
d = Demo()
print(d.greet) # Bound Method| Mistake | Why It Happens |
|---|---|
Missing self |
Python auto-passes instance |
| Calling instance method via class incorrectly | No instance provided |
| Using staticmethod when classmethod needed | No access to class |
| Hardcoding class names | Breaks inheritance |
| Shadowing class attributes | Confusing behavior |
| Mutable default arguments | VERY dangerous 🔥 |
class Test:
def add_item(self, item, lst=[]): # ❌ DANGEROUS
lst.append(item)
return lst✔ Shared across calls 😱
def add_item(self, item, lst=None):
if lst is None:
lst = []| Feature | Instance | Class | Static |
|---|---|---|---|
| Bound To | Object | Class | Nothing |
| First Param | self |
cls |
None |
| Access Instance Data | ✅ | ❌ | ❌ |
| Access Class Data | ✅ | ✅ | ❌ |
| Use Case | Object Behavior | Shared Behavior | Utility Logic |
✔ Methods = Functions inside classes
✔ Python auto-passes context
✔ Three types exist
✔ Binding mechanics matter
✔ super() respects method chain
✔ Descriptors control behavior
- Create class with all 3 method types
- Demonstrate method binding
- Break code by removing
self - Use classmethod with inheritance
- Implement utility staticmethod
- Show mutable default argument bug
- Predict outputs