Historical Context & Motivation
Before object-oriented programming (OOP) existed, programmers wrote long sequences of instructions that acted on separate data stored in variables. As programs grew larger, this approach became difficult to manage. Imagine trying to track every student in a school using hundreds of individual variables like student1_name, student1_grade, student2_name, and so on. The code would quickly become unreadable and error-prone. Programmers needed a way to bundle related data and behavior together into a single, reusable package.
The central question that OOP answers is this: how can we organize code so that data and the operations on that data stay together, making programs easier to write, understand, and maintain? The answer lies in classes and instances — the fundamental building blocks of object-oriented Python.
Core Principles & Definitions
To understand classes, you first need to grasp a few key ideas. A class is a blueprint or template that defines what data an object holds and what actions it can perform. An instance (also called an object) is a specific realization of that blueprint, created with its own unique data. Think of a class as a cookie cutter and each instance as an individual cookie — they share the same shape, but each can be decorated differently.
Class
class keyword.Instance (Object)
my_dog = Dog().The __init__ Method
The self Parameter
Attributes & Methods
Visual Explanation — From Class to Instance
In the diagram above, notice that all three instances share the same structure — they each have name, breed, and energy attributes — because they were all created from the same class. However, the actual values stored differ. This is the power of classes: you define the structure once, then create as many unique objects as you need.
How It Works — Anatomy of a Class Definition
Let's break down the exact syntax Python uses to define a class. Every class definition follows a consistent pattern: the class keyword, the class name (by convention in PascalCase), a colon, and then an indented block containing the __init__ method and any other methods.
The class Keyword and Naming
You begin every class definition with the class keyword followed by the name of the class. Python convention says class names should use PascalCase — each word starts with a capital letter, with no underscores. For example, StudentRecord rather than student_record. This helps you visually distinguish classes from regular functions and variables.
The __init__ Constructor
The __init__ method is where you set up an object's initial state. Python calls this method automatically every time you create a new instance. Its first parameter is always self, which refers to the newly created object. Any additional parameters become the data you pass in when creating the object. Inside __init__, you assign attributes using self.attribute_name = value.
Defining Methods
Methods are functions defined inside a class that describe what an object can do. Every method must include self as its first parameter so it can access the object's attributes. When you call a method on an instance — like buddy.bark() — Python automatically passes the instance as the self argument behind the scenes.
Creating Instances
To create an instance, you call the class name as if it were a function, passing the arguments that __init__ expects (excluding self). For example, buddy = Dog("Buddy", "Golden Retriever") creates a new Dog object and assigns it to the variable buddy. This process is called instantiation.
def bark(self): you write self in the definition, but you never pass it explicitly when calling. You just write buddy.bark() — Python fills in self for you.Detailed Breakdown — Inside a Complete Class
Student class with labeled callouts: ① the class name, ② the constructor, ③ instance attributes set via self, ④ custom methods, and ⑤ how to create instances.Let's walk through each labeled part. The class name (①) tells Python you're creating a new type called Student. The constructor (②) is the special __init__ method that initializes every new object. Inside it, instance attributes (③) are created using self.attribute_name. The custom methods (④) like enroll and introduce define actions the object can perform. Finally, instantiation (⑤) shows how we create real objects by calling the class name with arguments.
| Component | Syntax Example | Purpose |
|---|---|---|
| Class keyword | class Student: | Declares a new class (type) |
| Constructor | def __init__(self, ...): | Initializes new instance attributes |
| Instance attribute | self.name = name | Stores data unique to each object |
| Method | def enroll(self, course): | Defines behavior for the object |
| Instantiation | alice = Student("Alice", 10) | Creates a new object from the class |
Worked Example — Building a BankAccount Class
Let's build a practical class from scratch. We'll create a BankAccount class that tracks an owner's name and balance, and provides methods for deposits and withdrawals.
class keyword and write the __init__ method. We need the owner's name, and we'll start the balance at zero by default.class BankAccount:
def __init__(self, owner):
self.owner = owner
self.balance = 0.0deposit that takes an amount and adds it to the balance. We include a check to make sure the amount is positive.def deposit(self, amount):
if amount > 0:
self.balance += amount
return self.balancewithdraw method should check that the withdrawal amount does not exceed the current balance. If it does, we print a warning instead of allowing overdraft.def withdraw(self, amount):
if amount > self.balance:
print("Insufficient funds!")
else:
self.balance -= amount
return self.balanceacct1 = BankAccount("Alice")
acct2 = BankAccount("Bob")
acct1.deposit(500) # Alice's balance: 500.0
acct2.deposit(1000) # Bob's balance: 1000.0
acct1.withdraw(200) # Alice's balance: 300.0Classes vs. Other Approaches
You might wonder why we need classes when we already have dictionaries and plain functions. Both approaches can store and manipulate data, but classes offer significant organizational advantages as programs grow. Let's compare them side by side.
| Feature | Classes | Dictionaries + Functions |
|---|---|---|
| Data + behavior | Bundled together in one unit — methods live alongside the data they operate on | Separate — functions and dictionaries are not formally linked |
| Structure enforcement | The constructor guarantees every instance has the same attributes | No enforcement — easy to misspell a key or forget a field |
| Readability | acct.deposit(100) reads like natural language | deposit(acct, 100) is less intuitive |
| Reusability | Classes support inheritance, letting you extend and customize existing blueprints | No built-in mechanism for extending structures |
| Best for | Modeling real-world entities with complex behavior | Quick, simple data storage with minimal behavior |
Connection to Advanced OOP Concepts
Defining classes and creating instances is the foundation of object-oriented programming, but there is much more to explore. Once you're comfortable with the basics, you'll encounter advanced concepts like inheritance, polymorphism, and encapsulation — all of which build on the class-and-instance model you've just learned.
| Concept | What You Know Now | What You'll Learn Next |
|---|---|---|
| Inheritance | Each class is defined independently | A child class can inherit attributes and methods from a parent class, reducing code duplication |
| Polymorphism | Methods do one specific thing per class | Different classes can share method names but implement them differently |
| Encapsulation | All attributes are openly accessible | You can hide internal details using naming conventions (underscore prefixes) to protect data |
| Special methods | You know __init__ | Methods like __str__, __repr__, __len__ let your objects work with built-in Python features |
For now, focus on mastering class definitions and instance creation. These concepts form the bedrock on which every advanced OOP technique is built. Once you can confidently define a class, write an __init__ method, add custom methods, and create multiple instances, you'll be ready to tackle inheritance and beyond.
Practice Problems
Book with an __init__ method that accepts title and author as parameters and stores them as instance attributes. Then create two instances of the class.Rectangle with attributes width and height. Add two methods: area() that returns the area and perimeter() that returns the perimeter. Create an instance with width 5 and height 3 and call both methods.Playlist class for a music app. The constructor should take the playlist name and start with an empty list of songs. Include methods add_song(song_title), remove_song(song_title), and total_songs(). Show how two different playlists can have different songs.class Team:
members = []
def __init__(self, name):
self.name = name
def add_member(self, person):
self.members.append(person)
After creating a = Team("Alpha") and b = Team("Beta"), calling a.add_member("Eve") makes "Eve" appear in b.members too. Why?Summary
A class is a blueprint that defines attributes (data) and methods (behavior) for a new type of object in Python. You define a class using the class keyword with a PascalCase name. The __init__ constructor runs automatically when you create a new object, and it uses the self parameter to assign data to each individual instance.
An instance is created by calling the class name like a function — a process called instantiation. Each instance holds its own separate data, so changes to one object don't affect another. Classes bundle data and behavior together, making code more organized, readable, and reusable than using loose dictionaries and functions. Mastering class definitions and instance creation is the essential first step in object-oriented programming, paving the way for advanced concepts like inheritance, polymorphism, and encapsulation.