Technology  /  Python

🐍 Python 78 guides · updated 2026

From first variable to OOP, generators, and real projects — the language that runs everything from data pipelines to AI agents, taught the practical way.

Instance Variables and Methods in Python: Where Object State Lives

Every object in Python needs somewhere to keep its data. That somewhere is instance variables. Every object also needs behaviour — ways to act on or report its data. That behaviour comes from instance methods. Together, they define what an object is and what it can do.

Instance Variables: Data That Belongs to One Object

An instance variable is an attribute attached to a specific object. Two objects of the same class can hold completely different values for the same attribute name, and neither affects the other.

Instance variables are typically set in __init__() using the self prefix:

class Sensor:
def __init__(self, name, unit):
self.name = name # instance variable
self.unit = unit # instance variable
self.readings = [] # instance variable — starts empty for each sensor
temp_sensor = Sensor("Temperature", "°C")
pressure_sensor = Sensor("Pressure", "hPa")
temp_sensor.readings.append(22.3)
print(temp_sensor.readings) # [22.3]
print(pressure_sensor.readings) # [] — completely independent list

Each Sensor object gets its own readings list. Appending to temp_sensor.readings has no effect on pressure_sensor.

Declaring Instance Variables Outside __init__()

Python allows adding instance variables after object creation, but this is usually a mistake. Code that reads obj.x without knowing whether x was set is fragile.

class Player:
def __init__(self, username):
self.username = username
self.score = 0
p = Player("alice")
p.level = 5 # added after creation — works, but not recommended
print(p.__dict__) # {'username': 'alice', 'score': 0, 'level': 5}

The __dict__ attribute holds a dictionary of all instance variables. It is useful for debugging and serialisation but avoid relying on it in production logic.

The consistent pattern is: declare all instance variables in __init__(), even if they start as None or an empty collection. This makes the class’s interface explicit at a glance.

Instance Methods: Behaviour That Operates on Object State

An instance method is a function defined inside a class that automatically receives self as its first argument. Through self, the method can read and modify the object’s instance variables.

class BankAccount:
def __init__(self, owner, initial_balance=0):
self.owner = owner
self._balance = initial_balance # underscore signals "handle carefully"
self._transactions = []
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit amount must be positive")
self._balance += amount
self._transactions.append(("deposit", amount))
def withdraw(self, amount):
if amount > self._balance:
raise ValueError("Insufficient funds")
self._balance -= amount
self._transactions.append(("withdrawal", amount))
def statement(self):
lines = [f"Account: {self.owner}", f"Balance: £{self._balance:.2f}", "---"]
for kind, amount in self._transactions:
lines.append(f" {kind.capitalize()}: £{amount:.2f}")
return "\n".join(lines)
account = BankAccount("Morgan", 500)
account.deposit(200)
account.withdraw(75)
print(account.statement())

Output:

Account: Morgan
Balance: £625.00
---
Deposit: £200.00
Withdrawal: £75.00

The methods handle all the rules. Code that holds a BankAccount object calls deposit() and withdraw() without needing to know how the balance is stored or how transactions are tracked.

How self Works

self is not magic — it is a regular parameter. When you call account.deposit(200), Python translates that into BankAccount.deposit(account, 200). The object is passed as the first argument automatically.

class Greeter:
def __init__(self, name):
self.name = name
def greet(self):
return f"Hello, I'm {self.name}"
g = Greeter("Sam")
# These two calls are identical
print(g.greet()) # Hello, I'm Sam
print(Greeter.greet(g)) # Hello, I'm Sam

Both forms call the same code. Python’s method call syntax is syntactic sugar for passing the object as the first argument.

Instance Variables vs Class Variables

Knowing the difference prevents one of the most common OOP bugs in Python.

class Robot:
population = 0 # class variable — shared across ALL instances
def __init__(self, model):
self.model = model # instance variable — unique per robot
Robot.population += 1 # increment the shared counter
def __del__(self):
Robot.population -= 1
r1 = Robot("R2D2")
r2 = Robot("C3PO")
print(Robot.population) # 2
print(r1.model) # R2D2
print(r2.model) # C3PO

Accessing r1.population reads the class variable (Python looks up the chain when it does not find it on the instance). But assigning r1.population = 10 creates a new instance variable named population on r1, hiding the class variable — the class variable stays at 2. Always modify class variables through the class name: Robot.population.

Introspection: Examining Object State

Python offers several ways to inspect what an object holds at runtime.

class Article:
def __init__(self, title, author, word_count):
self.title = title
self.author = author
self.word_count = word_count
a = Article("Deep Work", "Cal Newport", 68000)
# Dict of instance variables
print(a.__dict__)
# {'title': 'Deep Work', 'author': 'Cal Newport', 'word_count': 68000}
# Check if an attribute exists
print(hasattr(a, "title")) # True
print(hasattr(a, "isbn")) # False
# Get attribute by name (useful when attribute name is a string)
print(getattr(a, "author")) # Cal Newport
print(getattr(a, "isbn", "N/A")) # N/A — default if not found

getattr with a default is particularly useful in serialisation code where you cannot guarantee every attribute is present.

Common Mistakes

Accessing self outside a method. The self reference only exists inside instance methods. You cannot use it in the class body directly (only in method definitions).

Shadowing instance variables with local variables. If you name a local variable the same as an instance variable inside a method, you may accidentally read the local one instead of self.x.

class Counter:
def __init__(self):
self.count = 0
def increment(self, count=1): # parameter named 'count' shadows nothing here,
self.count += count # but be careful with names
def reset(self):
count = 0 # this is a local variable
# self.count is unchanged!
self.count = 0 # this is what you meant

Previous > The __init__() Constructor        Next > Class Variables and @classmethod