Python Classes and Objects Explained

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.

Note

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.

Pro Tip

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.

Note

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.

class Dog __init__(self, name, breed) self.name = name self.breed = breed bark(self) describe(self) my_dog name = "Rex" breed = "Labrador" bark() describe() your_dog name = "Bella" breed = "Poodle" bark() describe() blueprint instances (objects)
The Dog class acts as a blueprint. Each call to Dog(...) produces a new, independent object with its own attribute data.

Key Takeaways

  1. A class is a blueprint, not an object. Writing class Dog: defines what a Dog is. You need to call Dog(...) to create an actual object.
  2. __init__ runs automatically at creation. Use it to set instance attributes — the data each individual object will carry around.
  3. self refers to the current instance. Python passes it automatically; you only supply the arguments that come after it when calling a method.
  4. Instance attributes belong to one object. Setting my_dog.name = "Rex" does not affect any other Dog object.
  5. 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.