A class in Python is a blueprint for creating objects — it bundles together data (attributes) and behavior (methods) into a single reusable unit. An object is a concrete instance created from that blueprint. If you've worked with strings or lists in Python, you've already been using objects: a string is an instance of the built-in str class, and it has both data (the characters) and methods (like .upper()).

Classes are the foundation of object-oriented programming (OOP) in Python. They let you model real-world things — a Car, a User, a BankAccount — as self-contained units instead of scattering related data and functions across your code.

Defining a Class and Creating Instances

You define a class with the class keyword. Inside it, methods are just functions, but their first parameter is always self, which refers to the specific instance the method is being called on.

class Dog:
    # Class attribute — shared by every instance
    species = "Canis familiaris"

    def __init__(self, name, age):
        # Instance attributes — unique to each object
        self.name = name
        self.age = age

    def bark(self):
        return f"{self.name} says Woof!"

# Creating two separate instances (objects) of Dog
dog1 = Dog("Rex", 3)
dog2 = Dog("Bella", 5)

print(dog1.bark())
print(dog2.name, "is", dog2.age, "years old")
print(dog1.species)

Output:

Rex says Woof!
Bella is 5 years old
Canis familiaris

Calling Dog("Rex", 3) runs the special __init__ method automatically — this is the constructor, and it's where you set up each object's starting state.

Instance Attributes vs Class Attributes

Notice species is defined directly inside the class body, outside any method — it's a class attribute, shared by every instance unless an instance overrides it. name and age, on the other hand, are instance attributes: each object gets its own independent copy, set through self inside __init__.

dog3 = Dog("Max", 2)
dog3.species = "Modified"  # only changes dog3's own copy

print(dog1.species)  # still the original class attribute
print(dog3.species)  # the instance-level override

Output:

Canis familiaris
Modified

Calling Methods

Methods are called on an instance using dot notation, just like accessing an attribute. Python automatically passes the instance as the first argument (self), so you never pass it explicitly — dog1.bark() is really shorthand for Dog.bark(dog1) under the hood.

Classes become far more useful once you build on this foundation with a closer look at the constructor, and later with inheritance, which lets one class reuse and extend another's behavior. If you're coming from writing standalone functions, think of a class as a way to group related functions and the data they operate on into one cohesive object.