Imagine tracking a player in a game: their name, health points (HP), and level. You could store these as separate variables or inside one dictionary:
Then you write functions that take this dict everywhere:
It works… until the code grows. You start passing
player = {'name': 'Ada', 'hp': 80, 'level': 3}Then you write functions that take this dict everywhere:
def attack(player): player['hp'] -= 10It works… until the code grows. You start passing
player, then maybe also a separate list of their skills and another function for damage calculation, all loosely coupled.The problem with loose data
When related data (name, HP) and the behaviours that act on it (attacking, healing) live in separate places, you must remember to keep them in sync. It's easy for one function to forget a field or mutate data incorrectly.
A
A
class bundles both together into one unit: an object.What a class gives you
class Player:
def __init__(self, name):
self.name = name
self.hp = 100
self.level = 1
def take_damage(self, amount):
self.hp -= amount__init__ runs when you create an object and sets up its starting state. Methods like take_damage live with the data they modify — no extra arguments needed to find where HP is stored.You've actually been using this pattern without knowing it: strings and lists are objects of built-in classes.
# A string has methods attached to its data
s = 'hello'
print(s.upper()) # HELLO — the method acts on s's own characters
# A list stores items AND provides operations
tasks = ['a', 'b']
tasks.append('c') # no need to pass tasks around separately