SECTION 1
Historical Context & Motivation
Before object-oriented programming (OOP) existed, programmers wrote code as long sequences of instructions with data stored in separate, unconnected variables. As programs grew larger, keeping track of which data belonged to which part of the program became a nightmare. Imagine trying to manage the stats for 500 video-game characters when every attribute — health, speed, name — is just a loose variable floating around. OOP emerged to solve this chaos by bundling data and the functions that operate on that data into a single unit called an object.
1967 • Simula Introduces Objects
1972 • Smalltalk Refines OOP
1991 • Python Is Born
2000s • Python Becomes Mainstream
The core question that instance attributes and methods address is straightforward: how does each individual object keep track of its own unique data, and how does Python know which object's data to use when you call a method? The answer lies in a special keyword — self — which acts as each object's personal name tag, pointing back to itself so Python always knows whose data it's working with.
SECTION 2
Core Principles & Definitions
To understand instance attributes and methods, you first need to grasp a few foundational ideas. A class is a blueprint that describes what data an object will hold and what actions it can perform. An instance is a specific object built from that blueprint — like how a particular house is built from an architectural plan. Each instance can have its own unique values even though they all share the same structure.
Instance Attribute
__init__ using self.attribute_name.Instance Method
self, which gives it access to that object's attributes.The self Parameter
The __init__ Method
KEY TAKEAWAY
self keyword is like the word "my" on the card — when a card says "my name," it always refers to the person holding that particular card.SECTION 3
Visual Explanation — How self Connects Objects to Their Data
The diagram below shows how a single class blueprint produces two separate instances, each with its own data. Notice how self inside each method always refers back to the specific instance that called it. When dog1.bark() is called, self points to dog1; when dog2.bark() is called, self points to dog2.
Dog defines the blueprint. Two instances — dog1 and dog2 — each store their own name and age. The self reference inside each instance always points back to that particular object.SECTION 4
How It Works — The Mechanics of self
Understanding the mechanism behind self is the key to mastering instance attributes and methods. When you write dog1.bark(), Python secretly translates this into Dog.bark(dog1). The object before the dot gets passed as the first argument to the method — and that first parameter is self. This is why every instance method must list self as its first parameter, even though you never pass it yourself when calling the method.
The __init__ Constructor
The __init__ method (pronounced "dunder init") is a special method that runs automatically the moment you create a new instance. Its job is to initialize instance attributes by attaching data to self. For example, self.name = name takes the value passed in through the name parameter and stores it as an attribute on the specific object being created. Without self, the variable would be local to the method and would disappear once __init__ finishes running.
Writing Instance Methods
An instance method is any function defined inside a class that takes self as its first parameter. Inside the method, you can read or modify instance attributes using self.attribute_name. Methods can also accept additional parameters after self — just like regular functions. The critical rule is: always use self to access or modify the object's data inside a method.
Common Mistake Alert
self in a method definition is the #1 beginner error. If you write def bark(): instead of def bark(self):, Python will raise a TypeError saying it received 1 argument but expected 0. That "extra" argument is the instance Python tried to pass in!SECTION 5
Anatomy of a Class — Detailed Breakdown
Let's dissect a complete class to see exactly where each piece fits. The diagram below annotates every part of a Student class, showing the relationship between the class definition, the constructor, instance attributes, and instance methods.
Student class showing the constructor (purple), instance attributes (amber), a modifier method (cyan), an accessor method (green), and a display method (pink).| Component | Syntax Pattern | Purpose |
|---|---|---|
| Class declaration | class ClassName: | Defines the blueprint; name uses CamelCase |
| Constructor | def __init__(self, ...): | Initializes attributes when an object is created |
| Instance attribute | self.attr = value | Stores data unique to each instance |
| Instance method | def method(self, ...): | A function that can read/modify the object's data |
| Calling a method | obj.method(args) | Python passes obj as self automatically |
Key syntax patterns for instance attributes and methods
SECTION 6
Worked Example — Building a BankAccount Class
Let's build a BankAccount class step by step. This example demonstrates how instance attributes store each account's unique balance, and how methods use self to deposit, withdraw, and check the balance.
Creating a BankAccount Class
- 1
Step 1 — Define the Class and Constructor
Start by writing the class header and the__init__method. We need two instance attributes:self.ownerandself.balance. We'll give balance a default value of 0.class BankAccount: def __init__(self, owner, balance=0): self.owner = owner self.balance = balance - 2
Step 2 — Add a Deposit Method
Thedepositmethod takesselfand anamountparameter. It adds the amount toself.balance. Notice that we useself.balance(not justbalance) to modify the object's stored data.def deposit(self, amount): self.balance += amount return self.balance - 3
Step 3 — Add a Withdraw Method with Validation
Thewithdrawmethod checks whetherself.balanceis large enough before subtracting. This shows how methods can use instance attributes to make decisions. If the balance is too low, we return a warning message instead.def withdraw(self, amount): if amount > self.balance: return 'Insufficient funds' self.balance -= amount return self.balance - 4
Step 4 — Create Instances and Test
Now we create two separate accounts. Each has its ownownerandbalance. Depositing into one account does not affect the other — proving that instance attributes are independent.acct1 = BankAccount('Alice', 100) acct2 = BankAccount('Bob') acct1.deposit(50) → acct1.balance is 150 acct2.deposit(200) → acct2.balance is 200 - 5
Step 5 — Verify Independence of Instances
After the operations above,acct1.balanceis 150 andacct2.balanceis 200. Each object'sselfpointed to a different instance during each method call, so the data stayed separate.acct1.balance → 150 | acct2.balance → 200 (completely independent)
SECTION 7
Instance vs. Class Attributes — Key Differences
Python also supports class attributes — variables defined directly inside the class body (outside any method) that are shared by all instances. It's important to understand how these differ from instance attributes so you choose the right tool for the job.
| Feature | Instance Attribute | Class Attribute |
|---|---|---|
| Where defined | Inside __init__ using self.x | Directly in the class body |
| Scope | Unique to each object | Shared across all objects |
| When created | Each time an instance is made | Once, when the class is defined |
| Typical use | Unique data like name, score, ID | Shared constants like species, school name |
| Example | self.name = "Alice" | species = "Canis lupus" |
Instance attributes vs. class attributes
WHEN TO USE WHICH
SECTION 8
Connection to Advanced OOP Concepts
Instance attributes and methods are the foundation of OOP, but Python offers more powerful features built on top of them. Understanding self and instance-level data prepares you for inheritance, encapsulation, and polymorphism — the three pillars of object-oriented design.
| What You Know Now | What's Coming Next |
|---|---|
| Instance attributes store per-object data | Encapsulation — using underscores to protect attributes from outside access |
| Instance methods use self to access data | Inheritance — child classes inherit methods from parent classes and can override them |
| Each object has its own methods | Polymorphism — different classes can have methods with the same name that behave differently |
| Regular methods with self | Class & static methods — methods that use cls instead of self, or no special first parameter at all |
| Attributes accessed directly | Properties (@property) — methods that look like attributes, adding validation or computed values |
How today's concepts connect to advanced OOP topics
Every advanced OOP pattern relies on the fact that self gives each object a persistent identity — a way to carry data and behavior together. Once you're comfortable creating classes with instance attributes and methods, you're ready to build complex, real-world programs that model anything from social media feeds to physics simulations.
SECTION 9
Practice Problems
Question 1: CONCEPTUAL
self as its first parameter. What would happen if you forgot to include it?Show answer
self parameter is how a method knows which specific object it's working with. When you call obj.method(), Python automatically passes obj as the first argument, which the method receives as self. If you forget to include self in the method definition, Python will raise a TypeError because the method receives one more argument (the instance) than it has parameters to accept. Without self, the method also cannot access any instance attributes.Question 2: BASIC
Rectangle with instance attributes width and height, and an instance method area() that returns the area. Then create a rectangle with width 5 and height 3 and print its area.Show answer
class Rectangle: def __init__(self, width, height): self.width = width; self.height = height and then def area(self): return self.width * self.height. To test: r = Rectangle(5, 3) followed by print(r.area()) which outputs 15. The __init__ stores width and height on the instance, and area() uses self to access both attributes for the calculation.Question 3: INTERMEDIATE
Counter class with a count attribute starting at 0. Write three methods: increment() adds 1, decrement() subtracts 1 (but never below 0), and reset() sets count back to 0. Trace through this sequence and give the final count: c = Counter(); c.increment(); c.increment(); c.increment(); c.decrement(); c.increment().Show answer
class Counter: def __init__(self): self.count = 0 with methods def increment(self): self.count += 1, def decrement(self): if self.count > 0: self.count -= 1, and def reset(self): self.count = 0. Tracing through: start at 0, increment three times to 3, decrement to 2, increment to 3. The final value of c.count is 3. Each method modifies self.count, which persists between method calls because it's stored on the instance.Question 4: APPLIED
Playlist class for a music app. It should store a name and a list of songs (initially empty). Include methods add_song(title), remove_song(title), and total_songs(). Explain why self.songs = [] should go in __init__ rather than as a default parameter like def __init__(self, name, songs=[]).Show answer
class Playlist: def __init__(self, name): self.name = name; self.songs = [] with def add_song(self, title): self.songs.append(title), def remove_song(self, title): if title in self.songs: self.songs.remove(title), and def total_songs(self): return len(self.songs). Using songs=[] as a default parameter is a famous Python gotcha: default mutable arguments are shared across all calls, meaning every Playlist would accidentally share the same list. By writing self.songs = [] inside __init__, each instance gets its own fresh list.Question 5: CRITICAL THINKING
Pet has __init__(self, name) setting self.name = name and self.tricks = []. There is a method def learn_trick(self, trick): self.tricks.append(trick) and another method def show_tricks(self): return self.tricks. The student runs: p1 = Pet('Max'); p2 = Pet('Bella'); p1.learn_trick('sit'); p1.learn_trick('shake'); p2.learn_trick('roll over'). What does p1.show_tricks() return? What does p2.show_tricks() return? Explain how self ensures the tricks stay separate.Show answer
p1.show_tricks() returns ['sit', 'shake'] and p2.show_tricks() returns ['roll over']. When p1.learn_trick('sit') is called, Python translates it to Pet.learn_trick(p1, 'sit'), so self refers to p1 and self.tricks is p1's list. When p2.learn_trick('roll over') is called, self refers to p2, so it appends to a completely different list. Because self.tricks = [] is created inside __init__ for each instance, each Pet has its own independent tricks list.SUMMARY
Lesson Summary
Summary
In this lesson, you learned that a class is a blueprint for creating objects, and an instance is a specific object built from that blueprint. Instance attributes are variables defined in the __init__ constructor using self.attribute = value, and each object gets its own independent copy. Instance methods are functions inside the class that take self as their first parameter, allowing them to read and modify the calling object's data.
The self parameter is the mechanism that connects each method call to the correct object — Python automatically passes the instance as the first argument when you write obj.method(). Remember: always include self in method definitions, always use self.x to access instance attributes, and understand that instance attributes are independent across objects. These fundamentals are the gateway to the full power of object-oriented programming in Python.