CHAPTER 25 · INTERMEDIATE4 min read

Classes and Objects

Bring related state and behavior together without turning every function into a class.

Python 3.12+Free to readModule 6 of 13
YOUR NEXT SMALL WINS

By the end, you’ll be able to…

  • Construct independent instances with __init__.
  • Distinguish instance state from shared class attributes.
  • Choose classes when state and behavior form a useful unit.

01An object combines state with operations

A dictionary is a useful record, but any caller can misspell a key or change a value without following a rule. A class gives a domain concept a named interface. For a study session, the stored state might be a topic and completed minutes; an operation adds a positive duration. The object is useful because it keeps the rule beside the state it protects.

A class is a definition; an instance is one object created from it. Calling the class invokes construction. __init__ initializes an already-created instance and normally returns None. The conventional first parameter self refers to the instance receiving the method call. Python supplies it when you call session.add_minutes(20).

study_session.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

02Instance attributes belong to each object

Assigning self.minutes stores state on that instance. Each StudySession therefore starts with its own counter. A name assigned directly in the class body is a class attribute, shared through attribute lookup. Class constants such as a unit label are often appropriate; a shared mutable list of every instance’s notes usually is not.

The problem becomes visible when one object appends to a class-level list and another unexpectedly sees the new element. Put per-object mutable containers inside __init__, allocating a fresh container for each construction. Do not confuse two references to one instance with two independent instances: assigning other = session creates an alias, not a clone.

independent_notes.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

03Give methods a coherent responsibility

A method should operate on the concept represented by the object. A bank account can validate a withdrawal and update its balance. It need not also know how to render an HTML page, write a CSV export, or send a network request. Keeping unrelated responsibilities separate reduces the number of reasons the class must change.

Return values instead of printing from domain methods when callers may need the result in different interfaces. A remaining_minutes method can return an integer for a command-line display, a test, or a web response. Separating decisions from presentation makes the same object useful in more than one environment.

04Choose objects deliberately

Use a class when several operations share meaningful state and must preserve rules over time. A plain function is often clearer for a one-off transformation, and a dictionary or dataclass can suit a simple record. Creating a class with no state and one method may add ceremony without clarifying the problem.

Python attributes are accessible by default. A leading underscore communicates that a name is an implementation detail; it is not an access-control barrier. Start with a small public interface and use tests to describe supported behavior. Avoid promising every internal attribute to callers, because that makes later refactoring harder.

LET IT CLICK

The ideas worth keeping.

  • Initialize independent state on self.
  • Remember that assignment creates aliases rather than copies.
  • Use classes to express coherent stateful concepts and rules.
TURN UNDERSTANDING INTO SOMETHING YOU CAN DO

Make it yours.

Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.

01

Build a bounded counter

Create Counter(limit) starting at zero. increment() must reject changes beyond the limit and return the new count. Demonstrate that rejected changes preserve state.

your_turn_1.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.pyPython
class Counter:    def __init__(self, limit):        if limit < 0:            raise ValueError("limit cannot be negative")        self.limit = limit        self.count = 0    def increment(self):        if self.count >= self.limit:            raise ValueError("limit reached")        self.count += 1        return self.countcounter = Counter(1)assert counter.increment() == 1try:    counter.increment()except ValueError:    passassert counter.count == 1

Validation occurs before mutation. The object groups the current count, its limit, and the only supported change.

02

Detect shared mutable state

Create a ReadingList with add(title). Construct two lists and verify adding to one does not change the other.

your_turn_2.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.pyPython
class ReadingList:    def __init__(self):        self.titles = []    def add(self, title):        if not title.strip():            raise ValueError("title cannot be blank")        self.titles.append(title.strip())first = ReadingList()second = ReadingList()first.add(" Python Tutorial ")assert first.titles == ["Python Tutorial"]assert second.titles == []assert first.titles is not second.titles

Allocating the list inside __init__ gives each instance an independent container. A class-level titles = [] would share it.

A SMALL QUESTION. A USEFUL NEXT STEP.

What stayed with you?

Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.

A LITTLE ACTIVE RECALL0 / 3 answered
01What happens when alias = original is executed for an ordinary instance?
02Where should a per-instance mutable list usually be created?
03Which scenario most strongly motivates a class?
⚡CODING CHALLENGE
🔒Required to Mark Lesson Complete

Build a bounded counter

Create Counter(limit) starting at zero. increment() must reject changes beyond the limit and return the new count. Demonstrate that rejected changes preserve state.

EXPECTED OUTPUT
[Process exited with code 0 - No output]
challenge.pyPython 3
Ctrl + ↵
Execution Console
Click "Run & Verify" (or press Ctrl + Enter) to test your code against the challenge.

Keep exploring.

The official references behind this chapter, when you want a second angle or the full details.

One chapter closer.

Reading is a start. Trying it yourself makes the connection.

Solve the hands-on coding challenge in the knowledge section to verify your code and unlock chapter completion.