Python — OOP Part 1: What Is Object-Oriented Programming, and Why Use It?
In this lesson we’ll cover Object-Oriented Programming (OOP).
OOP has a lot to cover, so I’ve split it into seven lessons (including this one):
- What is Object-Oriented Programming (OOP)? Why use it?
- Class and Instance
- Class Variable
- Class Method and Static Method
- Inheritance and Subclass
- Magic Method
- Property Decorator — Getters, Setters, Deleters
What is object-oriented programming? #
Wikipedia defines OOP like this:
Object-oriented programming (OOP) is a programming paradigm based on the concept of “objects,” which can contain data and code: data in the form of fields (often known as attributes or properties), and code in the form of procedures (often known as methods). Instead of viewing a computer program as a list of instructions, OOP sees it as a collection of independent objects. Objects exchange messages with each other and process data.
OOP makes programs flexible and easy to modify, so it’s widely used in large software development. It also makes programming easier to learn and maintain, and enables more intuitive code analysis. However, over-emphasizing the modeling of programs as objects has been criticized for not always reflecting the real world accurately.
A program exists to process data toward a specific goal. When you handle complex data with only loose functions, you start running into errors and bugs. OOP helps solve these problems and makes complex data easier to manage. So what is OOP? Think of it as: using a “blueprint” called a class to create new data types that group data and functions (called methods inside a class) into a logical unit.
Watch out!
When writing programs, two important points to always keep in mind:
- Don’t repeat the same code. DRY (Don’t Repeat Yourself).
- Code is always subject to change.
If you find yourself copy-and-pasting a lot when coding, it means you have a lot of duplicated code, which causes a lot of problems. Duplication has to be minimized. It increases coding time, creates painful bugs, and forces you to update many places when something changes. To reduce duplication, we typically use functions — define once, call wherever needed, and update in one place when changes are required.
OOP, like functions, removes duplicate code, reduces coding time, and simplifies code management. That said, avoid creating throwaway classes used only once.
Why use object-oriented programming? #
In this lesson we’ll first look at when to use a class, and from the next lesson on, we’ll cover class features and usage.
Let’s look at an example: building characters in a game we love. We’ll make a simple character with a name, health, damage, and inventory. Without classes, we might write something like:
Create a Python file called oop.py in any directory and save the following code:
hero_name = 'Iron Man'
hero_health = 100
hero_damage = 200
hero_inventory = [
{'gold': 500},
{'weapon': 'Laser'}
]We made a character named “Iron Man.” But a game with only one character isn’t a game. Let’s add more heroes and monsters.
# Hero 1
hero_1_name = 'Iron Man'
hero_1_health = 100
hero_1_damage = 200
hero_1_inventory = [
{'gold': 500},
{'weapon': 'Laser'}
]
# Hero 2
hero_2_name = 'Deadpool'
hero_2_health = 300
hero_2_damage = 30
hero_2_inventory = [
{'gold': 300},
{'weapon': 'Long Sword'}
]
# Hero 3
hero_3_name = 'Wolverine'
hero_3_health = 200
hero_3_damage = 50
hero_3_inventory = [
{'gold': 350},
{'weapon': 'Claws'}
]
# Monster 1
monster_1_name = 'Goblin'
monster_1_health = 90
monster_1_damage = 30
monster_1_inventory = [
{'gold': 50},
{'weapon': 'Spear'}
]
# Monster 2
monster_2_name = 'Dragon'
monster_2_health = 200
monster_2_damage = 80
monster_2_inventory = [
{'gold': 200},
{'weapon': 'Flame'}
]
# Monster 3
monster_3_name = 'Vampire'
monster_3_health = 80
monster_3_damage = 120
monster_3_inventory = [
{'gold': 1000},
{'weapon': 'Hypnosis'}
]Someone once said the foundation of programming is copy and paste… and we’ve copy-pasted diligently. 🥵 But this is clearly not great code by anyone’s standards. Let’s clean it up using lists:
hero_name = ['Iron Man', 'Deadpool', 'Wolverine']
hero_health = [100, 300, 200]
hero_damage = [200, 30, 50]
hero_inventory = [
{'gold': 500,'weapon': 'Laser'},
{'gold': 300, 'weapon': 'Long Sword'},
{'gold': 350, 'weapon': 'Claws'}
]
monster_name = ['Goblin', 'Dragon', 'Vampire']
monster_health = [90, 200, 80]
monster_damage = [30, 80, 120]
monster_inventory = [
{'gold': 50,'weapon': 'Spear'},
{'gold': 200, 'weapon': 'Flame'},
{'gold': 1000, 'weapon': 'Hypnosis'}
]Now “Iron Man” is index 0, “Deadpool” is index 1, and “Wolverine” is index 2 — we can access each character’s data via their index. But this code is bug-prone. Here’s an example:
hero_name = ['Iron Man', 'Deadpool', 'Wolverine']
hero_health = [100, 300, 200]
hero_damage = [200, 30, 50]
hero_inventory = [
{'gold': 500, 'weapon': 'Laser'},
{'gold': 300, 'weapon': 'Long Sword'},
{'gold': 350, 'weapon': 'Claws'}
]
monster_name = ['Goblin', 'Dragon', 'Vampire']
monster_health = [90, 200, 80]
monster_damage = [30, 80, 120]
monster_inventory = [
{'gold': 50, 'weapon': 'Spear'},
{'gold': 200, 'weapon': 'Flame'},
{'gold': 1000, 'weapon': 'Hypnosis'}
]
# function called when a hero dies
def hero_dies(hero_index):
del hero_name[hero_index]
del hero_health[hero_index]
del hero_damage[hero_index]
# <--- suppose the developer accidentally forgot del hero_inventory[hero_index] ^^;;
hero_dies(0)
template = 'Hero name: {}\nHero health: {}\nHero damage: {}\nHero inventory: {}'
print(template.format(hero_name[0],
hero_health[0],
hero_damage[0],
hero_inventory[0]))Save and run:
Hero name: Deadpool
Hero health: 300
Hero damage: 30
Hero inventory: {'gold': 500, 'weapon': 'Laser'}We added a function that removes a hero from the lists when their health reaches 0 and they die. But if the developer forgets even one line, “Deadpool” ends up wielding the dead “Iron Man’s” Laser.
We can fix this by grouping each hero’s data into a dictionary inside a list:
heroes = [
{'name': 'Iron Man', 'health': 100, 'damage': 200, 'inventory': {'gold': 500, 'weapon': 'Laser'}},
{'name': 'Deadpool', 'health': 300, 'damage': 30, 'inventory': {'gold': 300, 'weapon': 'Long Sword'}},
{'name': 'Wolverine', 'health': 200, 'damage': 50, 'inventory': {'gold': 350, 'weapon': 'Claws'}}
]
monsters = [
{'name': 'Goblin', 'health': 90, 'damage': 30, 'inventory': {'gold': 50, 'weapon': 'Spear'}},
{'name': 'Dragon', 'health': 200, 'damage': 80, 'inventory': {'gold': 200, 'weapon': 'Flame'}},
{'name': 'Vampire', 'health': 80, 'damage': 120, 'inventory': {'gold': 1000, 'weapon': 'Hypnosis'}}
]
print('# Before deleting Iron Man')
print(heroes)
del heroes[0]
print('\n# After deleting Iron Man')
print(heroes)# Before deleting Iron Man
[{'name': 'Iron Man', 'health': 100, 'damage': 200, 'inventory': {'gold': 500, 'weapon': 'Laser'}}, {'name': 'Deadpool', 'health': 300, 'damage': 30, 'inventory': {'gold': 300, 'weapon': 'Long Sword'}}, {'name': 'Wolverine', 'health': 200, 'damage': 50, 'inventory': {'gold': 350, 'weapon': 'Claws'}}]
# After deleting Iron Man
[{'name': 'Deadpool', 'health': 300, 'damage': 30, 'inventory': {'gold': 300, 'weapon': 'Long Sword'}}, {'name': 'Wolverine', 'health': 200, 'damage': 50, 'inventory': {'gold': 350, 'weapon': 'Claws'}}]Data handling is easier now, but if a hero’s inventory has nested bags with multiple items, the dicts and lists nest further and the code gets even messier. There’s also still a lot of repeated code. This is a great place to use OOP. With OOP we can eliminate repetition and use class features that dicts and lists don’t support — like inheritance.
I said earlier that a class is a blueprint for new data types. A blueprint is the design diagram for a building or car. Once you have the blueprint, you can stamp out as many cars of the same model as you want. Programming classes work the same way.
Heroes and monsters above hold the same kinds of data, so we can group them logically using a class:
# class definition
class Character:
def __init__(self, name, health, damage, inventory):
self.name = name
self.health = health
self.damage = damage
self.inventory = inventory
def __repr__(self):
return self.name
# create Character class objects
heroes = []
heroes.append(Character('Iron Man', 100, 200, {'gold': 500, 'weapon': 'Laser'}))
heroes.append(Character('Deadpool', 300, 30, {'gold': 300, 'weapon': 'Long Sword'}))
heroes.append(Character('Wolverine', 200, 50, {'gold': 350, 'weapon': 'Claws'}))
monsters = []
monsters.append(Character('Goblin', 90, 30, {'gold': 50, 'weapon': 'Spear'}))
monsters.append(Character('Dragon', 200, 80, {'gold': 200, 'weapon': 'Flame'}))
monsters.append(Character('Vampire', 80, 120, {'gold': 1000, 'weapon': 'Hypnosis'}))
template = '{}'
print('# Hero list')
print(heroes)
print('\n# Hero data')
for hero in heroes:
print(hero.__dict__)
print('\n# Monster list')
print(monsters)
print('\n# Monster data')
for monster in monsters:
print(monster.__dict__)
del heroes[0] # remove Iron Man from heroes
print('\n# Hero list (after delete)')
print(heroes)
print('# Hero data (after delete)')
for hero in heroes:
print(hero.__dict__)# Hero list
[Iron Man, Deadpool, Wolverine]
# Hero data
{'name': 'Iron Man', 'health': 100, 'damage': 200, 'inventory': {'gold': 500, 'weapon': 'Laser'}}
{'name': 'Deadpool', 'health': 300, 'damage': 30, 'inventory': {'gold': 300, 'weapon': 'Long Sword'}}
{'name': 'Wolverine', 'health': 200, 'damage': 50, 'inventory': {'gold': 350, 'weapon': 'Claws'}}
# Monster list
[Goblin, Dragon, Vampire]
# Monster data
{'name': 'Goblin', 'health': 90, 'damage': 30, 'inventory': {'gold': 50, 'weapon': 'Spear'}}
{'name': 'Dragon', 'health': 200, 'damage': 80, 'inventory': {'gold': 200, 'weapon': 'Flame'}}
{'name': 'Vampire', 'health': 80, 'damage': 120, 'inventory': {'gold': 1000, 'weapon': 'Hypnosis'}}
# Hero list (after delete)
[Deadpool, Wolverine]
# Hero data (after delete)
{'name': 'Deadpool', 'health': 300, 'damage': 30, 'inventory': {'gold': 300, 'weapon': 'Long Sword'}}
{'name': 'Wolverine', 'health': 200, 'damage': 50, 'inventory': {'gold': 350, 'weapon': 'Claws'}}We’ve implemented OOP using a Character class. If OOP doesn’t quite click yet, don’t worry — by the end of this OOP series, you’ll understand it completely. 😄
That wraps up the introduction to object-oriented programming. In the next lesson we’ll dig into specific class usage.