Python is an object-oriented language, which means almost everything in it is built around the idea of objects. Once you understand classes and objects, a huge portion of Python's behavior starts to make sense — including why strings have methods like .upper(), why lists have .append(), and why your own custom data types can work the same way.
Before object-oriented programming became widespread, code was organized mostly as a sequence of functions operating on separate pieces of data. The problem is that data and the functions that work on it end up scattered across a codebase. Classes solve this by letting you bundle related data and behavior together into one reusable unit.
What is a class?
A class is a blueprint. It describes what data a thing should hold and what actions it should be able to perform, but it does not create the thing itself. Think of a class the way you might think of a cookie cutter: the cutter defines the shape, but it is not a cookie. Every cookie you press out is an individual object made from that blueprint.
In Python, you define a class using the class keyword, followed by a name written in PascalCase (each word capitalized, no underscores), followed by a colon. Everything indented beneath it belongs to the class.
class Dog:
pass
That is a valid, complete class definition. The pass keyword is a placeholder that tells Python "there is nothing here yet." The class exists, it just does not do anything.
Python convention is to name classes in PascalCase (Dog, UserAccount, NetworkPacket) and functions or variables in snake_case (send_packet, user_name). This distinction helps readers know at a glance whether a name refers to a class or a function.
The __init__ method
A class with only pass is not very useful. You normally want objects created from your class to start out with some data. That is what __init__ is for. It is a special method — called a dunder method (short for "double underscore") — that Python calls automatically every time you create a new object from the class.
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
Two things are happening here that trip up many beginners: the self parameter and the dot notation on the left side of each assignment.
What self actually is
self is a reference to the specific object being created or acted upon. When Python calls __init__, it automatically passes the new object in as the first argument. You capture that with the name self — it is just a convention, not a reserved word, but you should always use it to avoid confusing other developers (and yourself).
The lines self.name = name and self.breed = breed are creating instance attributes. An instance attribute is a piece of data that belongs to one specific object. Every Dog object you create from this class will have its own name and breed, independent of every other Dog.
When you see self.something = value inside __init__, you are defining what data an object of this class will carry. When you see self.something later inside another method, you are reading that stored data back.
Creating objects from a class
Once a class is defined, you create objects from it by calling the class name like a function. This process is called instantiation, and the resulting object is called an instance.
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
my_dog = Dog("Rex", "Labrador")
your_dog = Dog("Bella", "Poodle")
print(my_dog.name) # Rex
print(your_dog.name) # Bella
Notice that when you call Dog("Rex", "Labrador"), you pass two arguments — but __init__ has three parameters (self, name, breed). Python fills in self automatically. You only supply the arguments that come after self.
Each object stores its own data. Changing my_dog.name has zero effect on your_dog.name. The class is the blueprint; the objects are the individual things built from it.
"A class is a way of bundling data and functionality together." — Python documentation
Adding methods to a class
A class can hold more than just data. You can define functions inside a class — these are called methods. Methods let your objects do things. Every method receives self as its first parameter, which is how it accesses the instance's own attributes.
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def bark(self):
print(f"{self.name} says: Woof!")
def describe(self):
print(f"{self.name} is a {self.breed}.")
my_dog = Dog("Rex", "Labrador")
my_dog.bark() # Rex says: Woof!
my_dog.describe() # Rex is a Labrador.
When you write my_dog.bark(), Python looks up bark on the Dog class and calls it, automatically passing my_dog as the self argument. The method then uses self.name to reach into that specific object and retrieve its name.
Methods that accept additional arguments
Methods are not limited to just self. You can add as many parameters as you need after it, the same way you would with a regular function.
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
self.tricks = []
def learn_trick(self, trick):
self.tricks.append(trick)
print(f"{self.name} learned '{trick}'!")
def show_tricks(self):
if self.tricks:
print(f"{self.name} knows: {', '.join(self.tricks)}")
else:
print(f"{self.name} has not learned any tricks yet.")
my_dog = Dog("Rex", "Labrador")
my_dog.learn_trick("sit")
my_dog.learn_trick("shake")
my_dog.show_tricks()
# Rex learned 'sit'!
# Rex learned 'shake'!
# Rex knows: sit, shake
Notice the new attribute self.tricks = [] in __init__. Each object gets its own separate list when it is created. Calling my_dog.learn_trick("sit") appends to my_dog's list only — it does not touch any other dog's list.
Mutable default values — like lists or dictionaries — should always be initialized inside __init__ as self.tricks = [], not as a default parameter like def __init__(self, tricks=[]). If you use a mutable default parameter, all instances share the same list, which causes subtle and confusing bugs.
Classes vs. instances at a glance
The distinction between a class and an instance is one that confuses many beginners, so it helps to see it laid out directly.
| Concept | What it is | Example |
|---|---|---|
| Class | The blueprint or template | Dog |
| Instance | A specific object built from the class | my_dog = Dog("Rex", "Labrador") |
| Instance attribute | Data stored on a specific object | my_dog.name → "Rex" |
| Instance method | A function defined in the class that operates on an object | my_dog.bark() |
self |
The object a method is currently acting on | Passed automatically by Python |
You can create as many instances of a class as you need. Each one is independent, holding its own data, while still sharing the same method definitions defined in the class.
Key Takeaways
- A class is a blueprint, not an object. Writing
class Dog:defines what a Dog is. You need to callDog(...)to create an actual object. __init__runs automatically at creation. Use it to set instance attributes — the data each individual object will carry around.selfrefers to the current instance. Python passes it automatically; you only supply the arguments that come after it when calling a method.- Instance attributes belong to one object. Setting
my_dog.name = "Rex"does not affect any other Dog object. - Methods are functions that know which object called them. Because they receive
self, they can read and modify that object's attributes.
Classes are the foundation of object-oriented Python. Once you are comfortable defining a class with __init__ and a few methods, you are ready to explore the next layer — inheritance, where one class can build on and extend the behavior of another.