Skip to main content

Lesson 5 · Implementing the Manhattan Class

Module 4 · Manhattan Navigation

Lesson 5 · Implementing the Manhattan Class

60 minPython classesFunction → method
👩‍🏫

Teacher mode is on. Toggle it off (bottom-right) to preview the student view.

Last lesson you wrote compute_path() as a standalone function. Now you'll wrap it in a Manhattan class — the planner from the module overview. The algorithm doesn't change one bit; this lesson is about the mechanics of turning a function into a method that remembers where the robot started.

Learning Objectives

By the end of this lesson you will be able to:

  • Explain why bundling data and behavior in a class is useful
  • Write an __init__ that stores the start position as self.position
  • Convert a function into a method: add self, and use self.position instead of a start parameter
  • Explain why local variables (current_row, path) don't need self.
  • Test the class by creating an instance and calling its method

Why a class?

With the plain function, computing three paths from the same corner means typing the start every time:

path1 = compute_path((0, 0), (2, 3))
path2 = compute_path((0, 0), (1, 1))
path3 = compute_path((0, 0), (3, 2))

We wrote (0, 0) three times. Move the robot's start and you'd fix it in every line. A class stores the start once, then reuses it:

nav = Manhattan((0, 0))
path1 = nav.compute_path((2, 3))
path2 = nav.compute_path((1, 1))
path3 = nav.compute_path((3, 2))

The start lives inside the nav object. This is the same idea as the LineSensor and LineTrack classes from Module 2 — a class bundles data (the position) with behavior (computing a path).

Knowledge Check

What's the main advantage of the Manhattan class over the standalone function?

Activity · Build the Manhattan class

Three steps. Watch what doesn't change: the algorithm is the one you already wrote and tested — all that moves is where the start position comes from.

Step 1 · Store the position with __init__

__init__ runs automatically when you create the object. Its job here is to save the starting position:

class Manhattan:
def __init__(self, start):
self.position = start

start is a tuple like (0, 0). self.position = start tucks it away so the object's methods can read it later. Test it right away:

nav = Manhattan((0, 0))
print(nav.position) # (0, 0)

The (0, 0) you passed in flows into start, then gets stored as self.position.

Knowledge Check

After nav = Manhattan((2, 1)), what does nav.position hold?

Step 2 · The method is the same algorithm

Here's the whole conversion in one picture. The function's start parameter disappears — the method reads self.position instead. Everything else is identical:

# LESSON 4: standalone function
def compute_path(start, destination):
current_row, current_col = start # ← uses the start parameter
...

# LESSON 5: method inside the class
def compute_path(self, destination):
current_row, current_col = self.position # ← reads the stored position
...

Only three things change: the function moves inside the class, it gains self as its first parameter, and start becomes self.position. The four while loops don't change at all.

Here's the complete class:

class Manhattan:
def __init__(self, start):
self.position = start

def compute_path(self, destination):
path = []
current_row, current_col = self.position
dest_row, dest_col = destination

while current_row < dest_row:
current_row = current_row + 1
path.append((current_row, current_col))
while current_row > dest_row:
current_row = current_row - 1
path.append((current_row, current_col))
while current_col < dest_col:
current_col = current_col + 1
path.append((current_row, current_col))
while current_col > dest_col:
current_col = current_col - 1
path.append((current_row, current_col))

return path
Knowledge Check

In the class version, where does the old start parameter's information come from now?

Step 3 · Test it

Create an instance and call the method. Compare the output to your hand-traced paths from Lesson 4:

nav = Manhattan((0, 0))
path = nav.compute_path((2, 3))
print("Path:", path) # [(1, 0), (2, 0), (2, 1), (2, 2), (2, 3)]
print("Steps:", len(path)) # 5

One subtle, important point: computing a path does not change nav.position. The method works with the local variables current_row and current_col, so self.position stays put — you can compute as many paths from the same start as you like:

print(nav.compute_path((1, 1))) # [(1, 0), (1, 1)]
print(nav.position) # still (0, 0)
Knowledge Check

You create nav = Manhattan((0, 0)) and call nav.compute_path((2, 3)). Afterward, what is nav.position?

Why not use self. everywhere?

A common instinct is to write self.current_row, self.path, and so on. Don't. The rule: self. is for data that must survive between method calls. The position does — other methods (and next lessons' Navigator) need it. But current_row, path, and dest_col are scratch values that only matter while compute_path is running; they're plain local variables.

Persistent

self.position

Stored in init, read by methods, survives between calls. Needs self.

Temporary

current_row, path

Created fresh each time compute_path runs, then discarded. No self. — they're local.

Real-world connections

The "store it once, reuse it" pattern of objects is everywhere in real software:

Games

Player objects

A player object remembers its position, health, and score across every method that acts on it.

Apps

User accounts

A user object holds your name and settings once; every feature reads them from the same object.

Robotics

Sensor drivers

Your Module 2 LineSensor stored its pin once, then every reading method used it — same idea.

Wrap-up

  • What are the three changes from function to method? (Move inside the class, add self, replace start with self.position.)
  • What does current_row, current_col = self.position do, and what's it called? (Tuple unpacking — pulls both numbers out at once.)
  • Which variables need self.? (Only data that persists — self.position; the rest are local.)

Resources