DSM
60 XP
30 minsIntermediate60 XP

Classes & Objects

You've been using objects since day one: 'hello'.upper(), my_list.append(), and soon df.groupby(). Every one is DATA carrying its own BEHAVIOR. Today you stop just using such types and start defining them — because some state you've been threading through function after function deserves a home of its own.

What you'll learn

  • Define a class with __init__ and create instances
  • Explain self and how method calls bind to instances
  • Distinguish class (blueprint) from object (instance)
  • Decide when a class beats dicts + functions
  • Recognize the class machinery inside pandas/sklearn calls

What

A class is a blueprint for a new type: what data its instances hold (attributes) and what they can do (methods). An object is one instance built from that blueprint. __init__ sets up each new instance; self is how methods reach the instance they belong to.

Why

When several functions all take the same bundle of state (account, balance, history...), the design is begging for a class. And every data science library is classes: DataFrame, LinearRegression, Figure — defining your own makes their APIs transparent instead of magical.

Where it's used

Modeling domain entities (accounts, sensors, experiments), pipeline components, sklearn-style estimators, and reading ANY library's source code.

Where this runs in production

scikit-learnEvery model is a class

LinearRegression() constructs an instance; .fit() mutates its state (learned coefficients); .predict() uses that state. The whole estimator API is this lesson.

pandasDataFrame is a class

pd.DataFrame(data) calls a constructor; df.shape reads an attribute; df.groupby() calls a method. Three syntaxes you'll now recognize as one mechanism.

AirbnbDomain objects in pipelines

Pricing pipelines model a Listing as a class — attributes for location and amenities, methods for fee calculation — so business rules live WITH the data they govern.

Theory

The core ideas, in plain language.

Syntax: `class BankAccount:` opens the blueprint (CapWords naming — classes are the one place Python doesn't use snake_case). Inside, def __init__(self, owner, balance) is the initializer: it runs automatically whenever you create an instance with BankAccount('Ada', 100.0), and its job is to attach the instance's starting data via self.owner = owner.
class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance

acct = BankAccount('Ada', 100.0)
print(acct.owner)    # Ada
print(acct.balance)  # 100.0

Calling the CLASS like a function builds an instance: Python creates a blank object, passes it to __init__ as self, your assignments attach the data, and the finished object comes back. acct.owner reads an attribute with dot notation — like a dict field, but with a fixed, documented shape.

Analogy: Blueprint and houses

A class is an architect's blueprint; objects are the houses built from it. One blueprint, many houses — each with its own address, paint color, and furniture (attribute VALUES differ), all sharing the same layout (the same attributes and methods EXIST). Renovating one house doesn't touch the others — but changing the blueprint changes every house built after. And 'self' is just the builder's phrase for 'THIS house, the one we're standing in'.

Key Concept
self: the instance, passed automatically

Every method's first parameter is self — the instance the method was called ON. acct.deposit(50) is sugar for BankAccount.deposit(acct, 50): Python passes acct as self for you. That's the entire mystery. Inside the method, self.balance is 'THIS account's balance' — which is how two accounts keep separate balances while sharing one method definition.

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount

a = BankAccount('Ada', 100.0)
b = BankAccount('Kai', 50.0)
a.deposit(25.0)
print(a.balance, b.balance)  # 125.0 50.0

One deposit method, two independent balances — self routes each call to the right instance's data. This is the closure-counter from Scope & Closures with an explicit, inspectable home for its state.

Key Concept
When a class earns its keep

Reach for a class when: several functions all pass around the same bundle of state; the state must stay CONSISTENT under defined operations (a balance only changes via deposit/withdraw); or you're building many instances of one shape with behavior. Stick with dicts + functions when it's pure data passing through transformations — most ETL rows never need to be objects. The smell that demands a class: def deposit(account_dict, amount), def withdraw(account_dict, amount)... every function taking the same first argument.

Watch out
Three rookie mechanics

1) Forgetting self in a method signature — 'takes 1 positional argument but 2 were given' means Python passed the instance and your def had no slot for it. 2) Writing balance instead of self.balance inside a method — that's a local variable that vanishes when the method returns (the Scope lesson, striking again). 3) BankAccount() vs BankAccount — with parentheses you get an INSTANCE; without, the class object itself (the function-reference distinction, one level up).

Visual Learning

See the concept, then explore it.

What acct = BankAccount('Ada', 100.0) actually does

Construction, step by step. Click each stage.

Worked Examples

Watch it built up, one line at a time.

Very EasyA minimal class

Model a temperature sensor with an ID and a unit.

Step 1 of 1

The full lifecycle in six lines: blueprint, construction, attribute access. Note __init__'s parameters become the constructor's signature.

Code
01class Sensor:
02 def __init__(self, sensor_id, unit):
03 self.sensor_id = sensor_id
04 self.unit = unit
05 
06s = Sensor('T-101', 'celsius')
07print(s.sensor_id, s.unit)
Output
T-101 celsius
Practice Coding

Your turn — write the code.

Your task

Build a Playlist class: __init__ takes a name and starts an empty track list; add(title, minutes) appends a (title, minutes) tuple; total_minutes() returns the summed duration; describe() returns '<name>: <n> tracks, <total> min'. Create one playlist, add three tracks, print describe().

Expected output
Focus: 3 tracks, 14.0 min

Write your solution in the editor on the right, then hit Run.

Exercises

Prove it. Reach 80% to complete the lesson.

Mastery Gate0% / 80% required
Easy0/2 solved

What is the relationship between a class and an object?

In acct.deposit(50), what does the method receive as self?

Medium0/3 solved

Inside a method you write `count = count + 1` intending to update the instance's counter, and get UnboundLocalError. Why?

ScenarioA module has grown: def open_ticket(t, ...), def assign(t, agent), def escalate(t), def close(t) — every function takes the same ticket dict, and a recent bug came from a function setting t['status'] to a typo'd value.

What does this design want to become?

Define a Counter class: __init__ starts count at 0; increment() adds 1; report() returns 'count: N'. Create one, increment three times, print report(). Expected output: count: 3

State on self:count lives as self.count, initialized in __init__
Methods mutate and read:increment updates state; report formats it
Hard0/2 solved

a = Sensor('T1'); b = Sensor('T1') — is a is b True, and what about a separate c = a?

Define Inventory: __init__ takes a name; add(item, qty) accumulates quantities in a dict (merging repeat items); missing(required) takes a set of item names and returns the sorted list of those not stocked. Create one, add ('bolt', 5), ('nut', 2), ('bolt', 3), then print the stock dict and missing({'bolt', 'washer', 'screw'}). Expected: {'bolt': 8, 'nut': 2} ['screw', 'washer']

Accumulating dict attribute:add uses the .get(k, 0) + qty pattern on self.stock
Set difference in a method:missing converts stock keys to a set and differences
Complete 80% more exercises to unlock.
Interview Prep

How this shows up in real interviews.

What is self, mechanically? Why do Python methods need it?

Show model answer

self is the instance a method was invoked on, received as the method's first parameter. The dot syntax is sugar: acct.deposit(50) is exactly BankAccount.deposit(acct, 50) — Python looks deposit up on the class, then calls it with the instance prepended. This is why one method definition serves every instance: the shared code reaches per-instance data only through self, so a.deposit(25) and b.deposit(25) touch different balances. It also explains the two classic errors — omitting self in the def gives 'takes 1 positional argument but 2 were given' (the auto-passed instance had no slot), and writing balance instead of self.balance creates a throwaway local under ordinary scoping rules. Worth adding: 'self' is convention, not keyword — but breaking the convention is a review rejection everywhere.

When do you model something as a class versus a dict plus functions?

Show model answer

Class triggers: multiple functions all take the same bundle of state (the same-first-argument smell); the state has INVARIANTS that operations must preserve — a balance that only moves via deposit/withdraw, counters that must update together; you're making many instances of one shape with behavior; or you're building to a lifecycle protocol like sklearn's init/fit/transform. Dict-plus-functions wins when data is transient and shape-flexible — rows flowing through an ETL pipeline rarely deserve classes, and JSON naturally stays dicts at the boundary. The middle ground matters too: a dataclass gives named, typed fields without ceremony when there's state but little behavior. The principle: classes are for state WITH rules; plain data structures are for data in transit. Wrapping everything in classes is as much a smell as wrapping nothing.

Explain sklearn's fit/transform pattern in terms of plain Python classes.

Show model answer

An estimator is a class whose lifecycle maps to three plain mechanisms. __init__ stores CONFIGURATION (hyperparameters) as attributes — no data seen yet. fit(data) computes LEARNED STATE from training data and stores it on self — a scaler's min/max, a model's coefficients — conventionally returning self so calls chain. transform/predict APPLIES that stored state to any data — crucially including data it never learned from, which is what makes train/test separation possible: fit on train only, transform both, and the test set never leaks information into the parameters. Once you see it, the API reads itself: attributes ending in underscore (scaler.min_, model.coef_) are the learned state fit attached; 'NotFittedError' means the None-sentinel state was still unset when transform ran. Being able to hand-write a 15-line MinMaxScaler with exactly this shape is a common and fair interview exercise.

Common Mistakes to Avoid

1) Missing self in a method def — the auto-passed instance has nowhere to land. 2) Bare names where you meant attributes — self.count updates the object; count is a local. 3) Forgetting parentheses: Sensor is the class, Sensor() is a new instance. 4) Initializing shared mutable state on the CLASS body instead of in __init__ — every instance shares one list (next lesson dissects this). 5) Classes for everything — plain data in transit stays dicts/lists. 6) Expecting two constructions with equal args to be the same object — every call builds fresh; aliasing rules from Variables still apply.

Ask the AI Tutor

Try these prompts in the AI Tutor panel: • 'Quiz me: trace self through these three method calls.' • 'Show me a dict+functions design and help me refactor it to a class.' • 'Explain why sklearn's fit returns self.' • 'Give me five designs and make me judge: class or dict?' • 'Interview mode: ask me to build a MinMaxScaler class and grade it.'

Glossary

Class — a blueprint defining a new type's attributes and methods (CapWords names). Object / instance — one value built from a class. __init__ — the initializer run on each construction. self — the instance a method was called on; auto-passed first argument. Attribute — data attached to an instance (self.balance). Method — a function defined in a class, called through an instance. Constructor call — ClassName(args), which allocates and runs __init__. isinstance(x, C) — is x an instance of C? Invariant — a consistency rule operations must preserve. dataclass — stdlib shortcut for state-heavy, behavior-light classes.

Recommended Resources

• Docs: 'Classes' in the official Python tutorial — sections 9.1–9.4 cover today's ground. • Read: the sklearn 'Developing estimators' guide intro — see fit/transform stated as a contract. • Practice: rebuild the closure-based rate limiter from Scope & Closures as a class, and compare the two designs honestly. • Next in DSM: attributes have more machinery than you've seen — Attributes & Methods covers class-level attributes, the shared-mutable trap, and method design.

Recap

✓ class defines a blueprint; calling it builds instances; __init__ furnishes each one. ✓ self IS the instance — acct.deposit(50) passes acct automatically. ✓ Attributes (self.x) hold per-instance state; methods are the operations that keep it consistent. ✓ Same-first-argument-everywhere is the signal to promote dicts+functions to a class. ✓ Objects obey all existing rules: aliasing, scoping, use in lists/dicts/max(key=...). ✓ sklearn/pandas APIs are this lesson: __init__ config, fit learns state, methods apply it. Next up: Attributes & Methods. The blueprint has depth: class attributes vs instance attributes, the shared-mutable trap, and how to design method surfaces that keep state honest.

scratchpad — preview this lesson's challenge anytime
playlist.pyPython
Ready
Output

Run your code to see the output here.