Classes and Objects in Python: A Beginner's Guide to Object-Oriented Thinking
How do Python classes and objects actually work? A clear, beginner-friendly breakdown of self, init constructors, instance variables, class variables, and object references.
Introduction
If you’ve written a few Python scripts, you’ve probably reached a point where your code starts to feel messy. You have a bunch of variables floating around, functions that operate on them, and no clear way to tell which data belongs with which function. This is a common stage in every developer’s journey, and it usually happens because procedural programming — writing code as a sequence of functions and variables — doesn’t scale well as your project grows.
Imagine you’re building a student management system. You start with a few variables:
name = "John"
age = 21
marks = 85
That’s fine for one student. But what happens when you have fifty students? You’d need fifty sets of variables, or messy lists and dictionaries trying to keep everything in sync. Add a few more real-world systems — an e-commerce product catalog, or a banking application — and the same problem shows up again: data and the functions that act on that data live in completely separate places, with nothing tying them together.
This is exactly the problem that classes and objects solve. They let you group related data and behavior into a single, reusable unit. This article walks you through the fundamentals — what a class is, what an object is, and how Python connects the two — using simple, beginner-friendly explanations.
What Is a Class?
A class is a user-defined data type. Think of it as a blueprint or template that defines two things:
- What an object knows — its state or data (for example, a student’s name and age)
- What an object can do — its behavior or actions (for example, a student can study or introduce themselves)
Here’s the part that trips up a lot of beginners: a class does not store any actual data. It’s a definition, not a container. A class is a logical entity — it exists as a concept in your code, describing the shape that something will take, but it doesn’t hold real values until you actually create something from it.
A useful analogy is an architectural blueprint for a house. The blueprint tells you there will be two bedrooms, a kitchen, and a garage — but you can’t sleep in a blueprint. You need an actual house built from it before that structure becomes usable.
Why Do We Need Classes?
Without classes, code tends to fall into a few predictable traps:
- Variable explosion — a separate set of variables for every entity you track
- Poor organization — data and the logic that operates on it are scattered across the file
- Difficult maintenance — a small change means hunting down every place related data is used
- Difficult scaling — adding new features means adding more disconnected variables and functions
Classes fix this by giving you:
- Encapsulation of related data — everything about a “student” or a “bank account” lives in one place
- Reusability — write the structure once, create as many objects as you need
- Better abstraction — model real-world entities (students, products, accounts) the way you naturally think about them
Creating a Class
In Python, you define a class using the class keyword, followed by a name written in PascalCase (also called CapWords) by convention:
class Student:
pass
You’ll notice the pass keyword inside the class. Python doesn’t allow empty code blocks — unlike Java or C++, where empty curly braces {} are perfectly valid, Python needs something inside an indented block, even if that something does nothing. pass is a placeholder that tells Python “there’s intentionally nothing here yet.” It lets you sketch out the structure of your program before filling in the details.
What Is an Object?
If a class is the blueprint, an object is the actual house built from it. An object is an instance of a class — a real, physical entity that exists in memory and holds actual values.
From a single Student class, you can create many different objects, each representing a different student:
student1 = Student()
student2 = Student()
student3 = Student()
Each of these is a separate object, just as multiple cars can be built from the same design, or multiple bank accounts can be opened using the same account structure. The class defines the shared structure; the objects hold the individual, real-world data.
Class vs Object
It helps to see the distinction laid out directly:
| Class | Object |
|---|---|
| Logical entity | Physical entity |
| A definition | An actual instance |
| Holds no memory for instance variables | Occupies memory |
| Typically created once | You can create many objects |
Keep this table in mind as you read the rest of this article — nearly every confusing beginner moment in OOP traces back to mixing up these two ideas.
The Object Creation Process
When you write student1 = Student(), a sequence of steps happens behind the scenes:
Class Definition
↓
Object Creation
↓
Memory Allocation
↓
Initialization
↓
Ready for use
Python first looks at the class definition, allocates memory for a new object, runs any initialization logic (more on this shortly), and then hands you back a reference to that newly created object — which is what gets stored in your variable.
Attributes: What an Object Knows
Attributes are pieces of data that belong to an object. You access and modify them using the dot operator:
student1.name = "John"
student1.age = 21
print(student1.name) # John
Here, name and age are attributes of student1. This is the “state” part of an object — the information it carries around with it.
Methods: What an Object Does
Methods are simply functions defined inside a class. They represent the behavior of an object — the actions it can perform, often using its own attributes in the process.
class Student:
def study(self):
print("Studying...")
def introduce(self):
print("Hi, I'm a student.")
A bank account object might have a deposit() method; a student object might have a study() method. The key idea is that methods and the data they act on are bundled together inside the same class.
State and Behavior: The Core Mental Model
This is one of the most important ideas in this entire article, so it’s worth stating plainly:
Objects know things, and objects do things.
- State is what an object knows — its attributes, like
name,age, orbalance. - Behavior is what an object does — its methods, like
study(),withdraw(), ordeposit().
Every object you’ll ever design in Python (or any object-oriented language) can be broken down into these two categories. When you’re modeling a real-world entity, ask yourself: what does this thing need to know, and what does it need to be able to do?
Understanding self
self confuses almost every beginner at first, mostly because it looks like it should be a special keyword — but it isn’t. self is just a regular parameter name (you could technically call it anything), and by convention, it refers to the current object the method is being called on.
Here’s the trick: when you call a method on an object, Python automatically passes that object in as the first argument. So this:
student1.study()
is really being converted, internally, into something like this:
Student.study(student1)
That’s it. self is simply how the method knows which object’s data to work with. Without it, a method would have no way of knowing whether it should modify student1’s attributes or student2’s.
The Constructor: __init__
Instead of setting attributes manually after creating every object, Python gives you a special method called __init__ that runs automatically the moment an object is created. This is called the constructor.
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
Now, creating a student and assigning its data happens in one step:
student1 = Student("John", 21)
Note that __init__ is not a keyword — it’s a special method name that Python recognizes and calls automatically. It’s also worth noting that __init__ doesn’t create the object itself; the object already exists in memory by the time __init__ runs. Its job is only to initialize — that is, set up — the object’s starting values. This is a subtle but important difference from constructors in languages like Java, where object creation and initialization are more tightly bundled into a single conceptual step.
Understanding self.attribute = parameter
This single line of code is one of the most misunderstood in beginner Python:
self.name = name
Here’s the clearest way to think about it:
- The right side (
name) is a temporary input value — it only exists while__init__is running, and it disappears afterward. - The left side (
self.name) is where that value gets permanently stored, attached to the object itself.
So the parameter name is just a delivery mechanism. self.name is the actual, lasting attribute on the object. Once __init__ finishes running, name (the parameter) is gone, but self.name (the attribute) lives on as part of that object.
Instance Variables
Attributes that are set using self — like self.name and self.age — are called instance variables. They belong to individual objects, are stored separately for every object you create, and their lifetime is tied directly to the lifetime of that object.
john = Student("John", 21)
alice = Student("Alice", 22)
print(john.age) # 21
print(alice.age) # 22
Changing john.age has no effect on alice.age — they’re completely independent, because each object stores its own copy.
Class Variables
Sometimes, though, you want a piece of data to be shared across every object of a class, rather than duplicated for each one. That’s what class variables are for. They’re defined directly inside the class (not inside __init__), and they’re stored once, at the class level.
class Student:
university = "Green Valley University" # class variable
def __init__(self, name, age):
self.name = name # instance variable
self.age = age
Every Student object shares the same university value unless it’s specifically overridden. This is useful for things like a company name, a fixed tax rate, or any piece of information that logically belongs to the category rather than to any one individual instance.
A Quick Note: Instance, Class, and Static Methods
You’ll eventually come across three flavors of methods in Python: instance methods (the regular kind, tied to a specific object via self), class methods (tied to the class as a whole), and static methods (which don’t depend on instance or class data at all). Since this article focuses specifically on the fundamentals of classes and objects, we won’t go deep into the differences here — that distinction deserves its own dedicated article, coming soon.
Object Identity
Every object you create occupies its own space in memory and has a unique identity. Python lets you inspect this using the built-in id() function.
a = Student("John", 21)
b = Student("John", 21)
print(id(a) == id(b)) # False — two separate objects
Even though a and b hold identical data, they are two distinct objects living at two different memory locations. Compare this to:
a = Student("John", 21)
b = a
print(id(a) == id(b)) # True — same object
Here, b isn’t a new object at all — it’s just another name pointing to the same object as a.
Objects Are References
This leads to one of the most important — and most commonly misunderstood — ideas in Python: variables don’t store objects directly, they store references to objects.
When you write b = a, you are not copying the data inside a into a new object called b. You’re simply creating a second label that points to the exact same object in memory. This means:
a = Student("John", 21)
b = a
b.age = 25
print(a.age) # 25, not 21!
Changing b.age also changes what you see through a, because a and b are just two different names for the same underlying object. This is a common source of bugs for beginners who expect assignment to behave like copying — it doesn’t. If you want an independent copy, you need to explicitly create one, but that’s a topic for another day.
Everything in Python Is an Object
Once you understand classes and objects, a deeper truth about Python becomes clear: almost everything in Python is an object, including the types you’ve been using all along.
print(type(10)) # <class 'int'>
print(type("hello")) # <class 'str'>
print(type([1, 2, 3])) # <class 'list'>
Integers, strings, lists, tuples, dictionaries, functions, and even classes themselves are all objects, built from their own underlying classes. This is part of why Python is described as a deeply object-oriented language — the object model isn’t just something you opt into when you write class Student; it’s baked into the language from the ground up.
Common Beginner Mistakes
As you start working with classes and objects, watch out for these frequent points of confusion:
- Confusing a class with an object — remember, a class is the blueprint; an object is the real thing built from it.
- Thinking
selfis a special keyword — it’s just a conventional parameter name referring to the current object. - Assuming
__init__creates the object — it initializes an object that already exists; it doesn’t create it. - Forgetting
self— leaving it out of a method definition will cause errors, since Python relies on it to know which object’s data to use. - Assuming assignment creates a copy — as shown above,
b = acreates a second reference to the same object, not an independent duplicate.
Practical Benefits of Classes
Stepping back, here’s why all of this matters in real projects:
- Organization — related data and behavior live together, not scattered across the codebase
- Readability — code that models real-world entities is easier to follow
- Maintainability — changes to a
Studentclass happen in one place, not fifty - Reusability — write the class once, create as many objects as you need
- Scalability — adding new attributes or methods doesn’t require restructuring everything
- Team collaboration — clear structures make it easier for multiple developers to work on the same codebase
Final Mental Model
If you take away one thing from this article, let it be this simple chain:
Class
↓
Defines structure and behavior
Object
↓
Stores actual data and uses that behavior
Attribute
↓
What the object knows
Method
↓
What the object does
Classes and objects aren’t abstract computer science trivia — they’re a practical tool for organizing code the way you already think about the real world. Once this foundation feels solid, you’ll be ready to explore the deeper principles of object-oriented programming — encapsulation, inheritance, polymorphism, and abstraction — as well as common design patterns, in future articles.