Python OOP: Classes and Objects for Beginners
Start object-oriented programming in Python — why OOP, defining a class, __init__, attributes and methods, and creating objects.

Picture a dog in your program. It has a name and an age, and it can bark. So far you'd track that with a name variable, an age variable, and a bark() function floating somewhere nearby, all loosely connected by you remembering they go together. A class lets you wrap the dog's data and the things it can do into a single object, so "the dog" is one thing you can pass around, copy, and reason about.
Why OOP?
Loose variables work fine until you have two dogs. Now you need dog1_name, dog1_age, dog2_name, dog2_age, and a bark() that you have to tell which dog is barking every single time. Add a third dog and the whole thing sags.
Object-oriented programming fixes this by bundling related data and behavior into one unit. A Dog object carries its own name and age, and it knows how to bark without being handed the details. You stop juggling parallel variables and start working with whole objects: one Dog, another Dog, each self-contained.
This pays off the moment your program models real things. A User with an email and a login() action. A BankAccount with a balance and deposit() / withdraw(). A shopping Cart that holds items and totals itself. Whenever data and the operations on that data belong together, a class keeps them together. For a one-off calculation you don't need a class, a plain function is lighter. OOP earns its keep when you have a thing with both state and behavior, and especially when you'll have many of that thing.
Defining a class
A class is the blueprint; objects are the houses you build from it. Here's the blueprint for a dog:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = ageThree pieces to read here. class Dog: starts the definition, and the convention is to capitalize class names (Dog, BankAccount, User). Inside it, __init__ is a special method Python runs automatically the moment you create a Dog. Its job is setup: it takes the starting values and stores them on the new object. The double underscores on each side are how Python marks these built-in hook methods, and you'll hear __init__ called the "constructor" or "initializer."
Then there's self. Every method's first parameter is self, and it refers to the specific object the method is working on. When you write self.name = name, you're saying "store this name on this dog." That's what keeps two dogs from sharing the same data: each one has its own self.
If a function is a reusable block of instructions, __init__ is a function that happens to live inside a class and run at birth. Same def, same parameters, same return behavior. The only new idea is self and the fact that Python calls it for you.
Attributes
Every Dog needs to remember its own name and age. Those stored values are its attributes, and they live on the object through self:
class Dog:
def __init__(self, name, age):
self.name = name # attribute
self.age = age # attributeself.name = name reads a little oddly the first time. The plain name on the right is the parameter that was passed in. The self.name on the left is a new attribute being stored on the object. They happen to share a label, but one is temporary (gone when __init__ finishes) and the other sticks to the dog for as long as it exists. Once it's set, you read it back later with dog.name.
You're not limited to what's passed in. You can set defaults too:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
self.tricks = [] # every new dog starts with an empty trick listEvery Dog now begins life with a name, an age, and an empty tricks list, all stored on the object itself.
Methods
A dog that can't bark is just a record. The behavior, the things an object can do, lives in its methods. They're defined with def inside the class, and like __init__ they take self first so they can reach the object's own data:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print(self.name + ' says woof!')bark doesn't need you to tell it the dog's name. It pulls self.name straight off the object it was called on. That's the real point of methods: the behavior already has the data it needs sitting right next to it. A free-floating bark() function would need the name passed in every time; a method just reaches for self.
Methods can take extra arguments beyond self, the same way any function takes arguments:
def add_trick(self, trick):
self.tricks.append(trick)Here self is the dog and trick is whatever you want to teach it. The method updates the dog's own tricks list.
Note
self is just the object the method is working on. Python passes it in automatically, so you write d.bark(), not d.bark(d). You include self when you define a method, but you never pass it when you call one.
Creating objects
A class on its own does nothing. It's a blueprint, and you have to build from it. You create an object (an "instance") by calling the class like a function:
d = Dog('Rex', 3)That line runs __init__ behind the scenes with name='Rex' and age=3, builds a fresh Dog, and hands it back. Notice you don't pass anything for self — Python wires that up. Now d is a real dog you can poke at. Read its attributes with a dot, call its methods with a dot and parentheses:
print(d.name) # Rex
print(d.age) # 3
d.bark() # Rex says woof!The key idea: each instance keeps its own data. Build a second dog and it's completely independent of the first.
Run it. rex.bark() prints Rex says woof! and luna.bark() prints Luna says woof!, because each method reads its own object's self.name. Change Luna's age and Rex's stays put. Two objects, one blueprint, separate lives.
Here's the same class with a method that takes an extra argument, so you can see attributes change over the object's life:
rex.tricks starts empty, then grows as you call add_trick. The data lives on the object, and the object's own methods change it.
Quick check
What does __init__ do in a Python class?
Recap and what's next
A class is a blueprint. __init__ runs once when you build an object and sets up its starting attributes. Attributes (self.name, self.age) are the data the object carries; methods (def bark(self):) are the behavior that lives right next to that data. You build an object by calling the class, d = Dog('Rex', 3), read its data with d.name, and call its behavior with d.bark(). Every instance keeps its own data, which is the whole reason this scales past two dogs.
That's the core of objects. Next we go deeper: how one class can build on another, and the special "dunder" methods (like __init__'s cousins) that let your objects work with +, print(), and len() like Python's built-in types do. Keep going with inheritance and dunder methods.

Written by
Rhythm Bhiwani
Engineer and relentless builder, happiest reverse-engineering hard problems until they click.
Enjoyed this?
Tap the heart to leave some love.
Be the first to react
Comments
Join the conversation.
Loading comments…


