Day 10 · ~16m

Python Classes: Model Your Data With Objects

Dicts are flexible. Too flexible. Learn why a class with __init__ catches mistakes before they reach production, and where the data itself becomes smart.

student (thinking)

So here's my real question. I've been using {"id": 101, "total": 200.0, "status": "paid"} for months and it works fine. Why do I need a class?

teacher (neutral)

Add a negative total to that dict. What happens?

student (focused)

Nothing. It just... accepts it.

teacher (serious)

Now ship that to the billing system. A customer gets charged a negative amount. Finance opens a ticket. You trace it back to a dict that was happy to hold nonsense because dicts don't have opinions about their contents.

student (surprised)

And a class would have caught that?

teacher (encouraging)

A class with a two-line __init__ that raises ValueError if total is less than zero. The error happens at creation time, not three services downstream at 2 AM. That's the whole argument for classes: your data learns to say no.

student (curious)

So it's like... building codes for data. The blueprint won't let you build a room with no floor.

teacher (proud)

I could not have said it better. That's exactly what I'm going to use.

student (focused)

Okay so show me the class version.

teacher (neutral)

Here's the dict version — what you write today:

order = {"id": 101, "customer": "Alice", "total": 200.0}
print(order["total"])  # 200.0
print(order["id"])     # 101

Now the class:

class Order:
    def __init__(self, order_id, customer, total):
        self.id = order_id
        self.customer = customer
        self.total = total

order = Order(101, "Alice", 200.0)
print(order.total)     # 200.0
print(order.id)        # 101
student (confused)

def __init__? What is that?

teacher (serious)

__init__ is the constructor. When you call Order(101, "Alice", 200.0), Python runs __init__ automatically. That method sets up the instance — the individual order.

student (thinking)

So every time I make an order, __init__ runs? It's like saying "when an Order is created, first do this."

teacher (encouraging)

Exactly. That's when you set up the rules. Watch:

class Order:
    def __init__(self, order_id, customer, total):
        if total <= 0:
            raise ValueError("Order total must be positive")
        self.id = order_id
        self.customer = customer
        self.total = total

order = Order(101, "Alice", 200.0)
print(order.total)  # Works: 200.0

bad_order = Order(102, "Bob", -50.0)  # ValueError immediately
student (excited)

OH. The ValueError happens right when you try to create the bad order. Not later. Not somewhere downstream. Right there.

teacher (focused)

Right there. The class says "I will not exist with invalid data." A dict has no such opinion.

student (thinking)

What's self though? That looks weird.

teacher (neutral)

self refers to the instance — the specific order you're creating. When you write self.id = order_id, you're saying "this order's id is...". It's a bit redundant, but Python needs it for a technical reason we don't have time to explain today. Just remember: self is the object itself.

student (confused)

So I always have to write self as the first parameter?

teacher (serious)

Always. It's a quirk of Python's design. Every method in a class takes self as the first argument. Python fills it in automatically when you call the method — you don't write it:

order = Order(101, "Alice", 200.0)
# You call:   Order(101, "Alice", 200.0)
# Python calls:   __init__(order, 101, "Alice", 200.0)
#                 ^^ self is the new order object
student (amused)

So Python handles the first argument, and I write the rest. That's... inelegant. But okay.

teacher (amused)

The person who designed this has since apologized. I'm paraphrasing.

student (focused)

What about the class Order: line? Is that the blueprint?

teacher (neutral)

Exactly. class Order: is the blueprint. It defines the structure: what data an order has, what rules it follows, what methods it can do. Every time you call Order(...), you're creating a new instance — a building made from that blueprint.

student (thinking)

So the instance is the actual order. The class is the template.

teacher (encouraging)

You've got it. A class is a blueprint. An instance is a building. The blueprint says "all buildings must have valid totals." The building follows that rule.

student (curious)

Can I create the Order class and then build one myself?

teacher (focused)

Yes. Your turn. Define a class called Order with __init__ that takes order_id, customer, and total. Set those as instance attributes using self. Then add a check: if total is less than or equal to zero, raise a ValueError with the message "Order total must be positive."

student (focused)

Okay, let me think through this. def __init__(self, ...) creates the instance. self.id = order_id stores it. And if total is bad, I raise ValueError.

teacher (neutral)
class Order:
    def __init__(self, order_id, customer, total):
        if total <= 0:
            raise ValueError("Order total must be positive")
        self.id = order_id
        self.customer = customer
        self.total = total
teacher (excited)

Done. That's a complete class. Test it:

valid = Order(101, "Alice", 200.0)
print(valid.id)  # 101
print(valid.total)  # 200.0

invalid = Order(102, "Bob", -50.0)  # ValueError
student (proud)

The valid one works. The invalid one blows up at creation. No dict would ever do that.

teacher (serious)

And that's why the codebase has classes. Every time Amir creates an order, he knows it's already been checked. The data is not just stored — it's validated.

student (thinking)

But what can an Order actually do right now? I can read the fields. That's it.

teacher (curious)

What do you want it to do?

student (focused)

I don't know. Apply a discount? Calculate tax? Right now it's just a container.

teacher (neutral)

That's tomorrow's lesson — methods. An instance can have functions attached to it. Methods that do work specific to that object. A method that says "apply a 10% discount to this order." Or "send confirmation email for this order." Or "calculate total with tax."

Today is the structure. Tomorrow is the behavior. You can't have behavior without structure.

student (curious)

So the Order class exists now. It validates. But it's waiting for methods?

teacher (encouraging)

Exactly. The blueprint is solid. Tomorrow we build the rooms inside.

student (thinking)

And once I add methods, the Order will be able to do things that a dict could never do — because the code can assume the data is always valid.

teacher (proud)

Now you're thinking like Amir.