Object-Oriented Programming Basics
Learn the fundamentals of object-oriented programming: classes, objects, attributes, and methods.
Thinking in objects
So far you have been writing code in a sequence, instruction by instruction. Object-oriented programming (OOP) is a different way to organize your code. Instead of thinking about steps, you think about objects.
An object is something that has properties, called attributes, and can do things, called methods. A dog has a name, a breed and an age. A dog can bark, eat and sit. In OOP, you create a blueprint called a class, and then you create individual objects from that blueprint.
- Class: the blueprint or recipe, such as "Dog"
- Object (instance): one real thing made from the blueprint, such as Rex
- Attribute: data that belongs to the object, such as its name or age
- Method: a function that belongs to the object, such as bark
An easy way to think about it: a cookie cutter is the class, and each cookie it makes is an object. They all share the same shape, but each cookie can have different decoration. Why bother? When a program grows, grouping related data and actions together keeps things organized. Python itself is built this way. Strings, lists and dictionaries are all objects with methods, and that is why you can write "hi".upper().
Creating a class
class Dog:
def __init__(self, name, breed, age):
self.name = name
self.breed = breed
self.age = age
def bark(self):
print(f"{self.name} says: Woof!")
def describe(self):
print(f"{self.name} is a {self.breed} and is {self.age} years old.")
Break it down:
class Dog:starts the blueprint. Class names use CapitalizedWords by habit.__init__is the constructor. Python calls it automatically when you create a new object. The double underscores on both sides are part of the name.selfrefers to the object itself. Every method gets it as the first parameter, and you do not pass it when calling.self.name = nameattaches the value to this specific object, so the object remembers it.
The class by itself does nothing yet. It is only the blueprint. You have to create an object from it, which is the next card.
Creating objects
dog1 = Dog("Rex", "German Shepherd", 3)
dog2 = Dog("Bella", "Labrador", 5)
dog1.bark() # Rex says: Woof!
dog2.bark() # Bella says: Woof!
dog1.describe() # Rex is a German Shepherd and is 3 years old.
Writing Dog(...) with brackets creates a new object and runs __init__. The three values you pass go to name, breed and age (the self is filled in for you). Calling dog1.bark() runs the method with self set to dog1, which is why it says Rex.
Each object has its own separate copy of the attributes. Changing dog1.name does not affect dog2.name. You can make as many objects from one class as you like, and each one lives on its own.
Reading and changing attributes
print(dog1.name) # Rex
dog1.age = 4 # change the age
print(dog1.age) # 4
Use a dot to reach an attribute, both for reading and for changing. Changing dog1.age leaves dog2.age at 5, because they are separate objects. You can even add a new attribute to a single object after creating it, though it is cleaner to set everything in __init__.
A method can also change the object. Here is a small example, a Pet class with a birthday method:
class Pet:
def __init__(self, name, age):
self.name = name
self.age = age
def have_birthday(self):
self.age += 1
rex = Pet("Rex", 3)
rex.have_birthday()
print(rex.age) # 4
A method can also give back a value with return, just like a normal function. And if you print an object directly, you get an unhelpful text like <__main__.Dog object at 0x...>. You can fix that by adding a __str__ method that returns a nice string.
A practical example: a bank account
Objects shine when data and the actions on that data belong together. A bank account has a balance, and actions like deposit and withdraw must follow rules:
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
if amount > 0:
self.balance += amount
print(f"Deposited ${amount:.2f}. New balance: ${self.balance:.2f}")
else:
print("Deposit amount must be positive.")
def withdraw(self, amount):
if amount > self.balance:
print("Insufficient funds.")
elif amount <= 0:
print("Withdrawal amount must be positive.")
else:
self.balance -= amount
print(f"Withdrew ${amount:.2f}. New balance: ${self.balance:.2f}")
def show_balance(self):
print(f"Account owner: {self.owner}")
print(f"Current balance: ${self.balance:.2f}")
account = BankAccount("Alice", 1000)
account.show_balance()
account.deposit(500)
account.withdraw(200)
account.withdraw(2000)
account.show_balance()
Account owner: Alice
Current balance: $1000.00
Deposited $500.00. New balance: $1500.00
Withdrew $200.00. New balance: $1300.00
Insufficient funds.
Account owner: Alice
Current balance: $1300.00
The rules live inside the class. Nobody has to remember to check for enough money. They just call withdraw, and the object protects itself. Also notice the default value balance=0, which works exactly like default parameters in functions.
Inheritance
A class can inherit from another class. The child class gets all the attributes and methods of the parent, and can add its own or override existing ones:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print("...")
class Dog(Animal):
def speak(self):
print(f"{self.name} says Woof!")
class Cat(Animal):
def speak(self):
print(f"{self.name} says Meow!")
animals = [Dog("Rex"), Cat("Whiskers"), Dog("Bella")]
for animal in animals:
animal.speak()
Rex says Woof!
Whiskers says Meow!
Bella says Woof!
Writing class Dog(Animal) means Dog is a kind of Animal. Dog has no __init__ of its own, so it uses the parent's one. Both children override speak, so each gives its own answer. The loop calls the same method name on different objects and each behaves in its own way. That idea is called polymorphism, which is a fancy word for "many forms".
Inheritance saves you from copying code. Put what is shared in the parent, and only the differences in the children.
Making objects print nicely
Python has special methods with double underscores that let your objects behave like built-in ones. The most useful for beginners is __str__, which controls what print() shows:
class Book:
def __init__(self, title, pages):
self.title = title
self.pages = pages
def __str__(self):
return f"{self.title} ({self.pages} pages)"
book = Book("Python Basics", 250)
print(book) # Python Basics (250 pages)
Without __str__, you would see something like <__main__.Book object at 0x7f...>, which tells you nothing. Another handy one is __len__, which lets len(obj) work on your own objects.
When a child class has its own __init__ and still needs the parent's setup, call super().__init__(...):
class Puppy(Dog):
def __init__(self, name, breed):
super().__init__(name, breed, 0) # age starts at 0
When to use classes
You do not need a class for everything. Here is a simple guide:
- Use a function for a single action that takes input and gives output.
- Use a dictionary for a simple bag of related values.
- Use a class when you have several objects of the same kind, each with its own data, and actions that work on that data.
Good first ideas to practice with:
- A
Carwith make, model and speed, and methodsaccelerateandbrake - A
Studentwith a name and a list of grades, and a method that returns the average - A
Todoitem with a description and a completed flag, and a methodcomplete
class Student:
def __init__(self, name):
self.name = name
self.grades = []
def add_grade(self, grade):
self.grades.append(grade)
def average(self):
return sum(self.grades) / len(self.grades)
s = Student("Maya")
s.add_grade(90)
s.add_grade(80)
print(s.average()) # 85.0
A common mistake is forgetting self. If you write def bark(): inside a class, Python complains that it got an unexpected argument. Another is writing name = name instead of self.name = name, so the value is never saved on the object.
Key takeaways
- A class is a blueprint. An object is one thing made from it
- Attributes are the data, and methods are the actions
__init__runs automatically when an object is created and sets up its attributesselfmeans this particular object. Useself.nameto store and read its data- Each object keeps its own copy of the attributes
- A child class
class Dog(Animal)inherits everything and can override methods - Practice by modeling real things: a Car, a Student, a Todo item