Thinking in Objects

Advanced · 16 min read · ▶ live playground · ✦ checkpoint

Your programs are about to outgrow "do this, then do that." When the data starts traveling with the same helper functions again and again, Python gives you a different question: what things live in this program, and what should each thing know how to do?

From steps to things

Most of the Python you've written so far has been procedural programming, which means organizing a program around steps: data sits in values, and functions operate on that data. That's a great way to begin because it matches the "very fast, very literal assistant" picture from the start of the course.

Here is a small procedural program. A player is a dictionary, and the functions receive that dictionary whenever they need to change it:

player = {"name": "Mina", "health": 10, "mood": "brave"}
 
 
def take_hit(player, damage):
    player["health"] = player["health"] - damage
    player["mood"] = "shaken"
 
 
def encourage(player):
    player["mood"] = "determined"
 
 
take_hit(player, 3)
encourage(player)
 
print(f"{player['name']} has {player['health']} health.")
print(f"{player['name']} feels {player['mood']}.")

Mina has 7 health.
Mina feels determined.

Nothing is wrong here. The code is clear enough. But notice the shape: the player data lives in one place, while player actions live somewhere else. Every function has to be handed the player before it can do player work.

Object-oriented programming (OOP) means organizing a program around objects, where an object is a live value in memory that can carry data and have actions attached. Instead of asking, "What steps happen next?", you also ask, "What thing is responsible for this data and this behavior?"

An object's state is the data it currently carries. An object's behavior is what it can do through attached actions. A method is a function-like action attached to an object; you've already used methods when you wrote string tools like .upper() and list tools like .append().

You already use objects

Python doesn't save objects for advanced code. Every value you've used has already been an object floating in memory, with variables acting as name tags tied to those objects.

Run this with that model in mind:

name = "Ada Lovelace"
scores = [8, 10]
profile = {"mood": "focused"}
 
print(type(name))
print(name.upper())
 
scores.append(9)
print(scores)
 
print(profile.get("mood"))

<class 'str'>
ADA LOVELACE
[8, 10, 9]
focused

That first line of output is the clue. type(name) says the text object belongs to the str class. The list object has list behavior, so it understands .append(). The dictionary object has dictionary behavior, so it understands .get().

A class is a blueprint for a kind of object. An instance is one concrete object made from that class. The blueprint says what this kind of object can carry and do; each instance has its own actual data.

Think of a class like a house blueprint and an instance like one house built from it. The blueprint can say "bedrooms, doors, kitchen." One house can be blue with three people inside, while another is white with one person inside. Same blueprint shape, different real house.

Python's built-in classes work the same way:

>>> type("Ada")
<class 'str'>
>>> type("Mina")
<class 'str'>
>>> type(["apples", "tea"])
<class 'list'>
>>> type({"mood": "calm"})
<class 'dict'>

"Ada" and "Mina" are different string instances. They have different state because they carry different text. They share behavior because both are made from the str class, so both know methods like .upper(), .lower(), and .split().

The class statement that creates your own blueprint comes in lesson 10.2. For now, you only need the mental split: class is the reusable blueprint; object or instance is the live value built from it.

Try it — spot the object-shaped code

This is still plain Python you already know: dictionaries, functions, strings, numbers, and prints. The goal is to notice which data and actions naturally travel together.

python — playgroundlive
⌘/Ctrl + Enter to run

If you changed player to actor, the program still makes sense. That is a hint. You are not really writing "player code" and "guide code"; you are writing code for actor-shaped things.

In procedural style, the function stands outside the data:

  • describe(actor)
  • spend_energy(actor, 2)

In object style, the action moves next to the thing it belongs to. You will soon write code that reads like actor.describe() and actor.spend_energy(2). The dot does not make the idea magical; it just means you are asking the object for one of its attached names, the same way name.upper() asks a string object for its uppercase method.

Objects collaborate

Object thinking also changes how you picture the whole program. A larger program is not one enormous function with every dictionary in its pockets. It is a set of objects sending work to each other.

Here is a library example in procedural form:

library_card = {"owner": "Ira", "checked_out": []}
book = {"title": "Python Notes", "available": True}
 
 
def check_out(card, book):
    if book["available"]:
        book["available"] = False
        card["checked_out"].append(book["title"])
 
 
check_out(library_card, book)
 
print(f"{library_card['owner']} borrowed {library_card['checked_out'][0]}.")
print(f"Book available? {book['available']}")

Ira borrowed Python Notes.
Book available? False

Two things changed: the card gained a title, and the book stopped being available. In an object-oriented design, you would ask which objects should own those changes. Maybe a Book knows whether it is available. Maybe a LibraryCard knows which titles are checked out. Maybe a Library coordinates the checkout.

That last sentence is the heart of OOP. You model the problem as things with clear jobs, not as one long script where every function can touch every detail.

When object thinking helps

OOP is not a prize for making code look advanced. A short script that cleans one CSV file can stay procedural forever and be perfectly readable. Objects start paying rent when your program has several related pieces of state and behavior that keep appearing together.

Good object-shaped questions sound like this:

  • What nouns keep showing up in the problem: player, book, account, order, task?
  • What data belongs to each noun?
  • What actions mostly affect that data?
  • Which objects need to talk to each other?

Bad object-shaped questions sound like this:

  • How can I turn every function into a class?
  • How can I make this look more professional?
  • How can I hide simple code behind more names?

Use OOP when it makes the model clearer. The point is not more syntax. The point is a program whose structure matches the world it is trying to represent.

Checkpoint

Answer all three to mark this lesson complete

Next, you stop pointing at Python's built-in blueprints and write your own. In lesson 10.2, class creates the blueprint, __init__ sets up each new object, and self lets an object refer to itself.

+50 XP on completion