Python Arena
Step 4 of 4

Helper methods and local variables

As a class grows, some of its methods are there only to help the others. A helper method is called through self, from the constructor or from another method. This booking class checks its arguments with one:

class Booking:
    def __init__(self, name, guests, nights):
        self.name = name
        self.guests = 1
        self.nights = 1
        if self.in_range(guests, 8):
            self.guests = guests
        if self.in_range(nights, 30):
            self.nights = nights

    def in_range(self, number, largest):
        return 1 <= number <= largest

    def price(self):
        per_night = 50 + 20 * (self.guests - 1)
        return per_night * self.nights


kim = Booking("Kim", 3, 2)
lee = Booking("Lee", 12, 0)
print(kim.guests, kim.nights, kim.price())
print(lee.guests, lee.nights, lee.price())
Output
3 2 180
1 1 50

The constructor first gives every attribute a safe value, then uses self.in_range to decide whether the arguments can replace them. Lee's booking had impossible numbers, so it keeps the safe ones. Forget the self. in front of a helper method, and Python stops with NameError: name 'in_range' is not defined. Did you mean: 'self.in_range'?

Local variables

Look at per_night in price. It has no self. in front, so it is an ordinary local variable: it exists only while the method runs, like a variable inside any function. Use attributes for what the object must remember from one call to the next, and local variables for the steps of a calculation.

What an object needs to remember

An object doesn't have to keep a record of everything that happens to it, only what its methods need. A habit tracker that reports your current streak (how many days in a row you have practiced, up to the last one) and your longest streak needs two numbers, not a list of every day:

class Streak:
    def __init__(self):
        self.current = 0
        self.longest = 0

    def log(self, done):
        if done:
            self.current += 1
            if self.current > self.longest:
                self.longest = self.current
        else:
            self.current = 0


guitar = Streak()
while True:
    answer = input("Practiced today? ")
    if answer == "":
        break
    guitar.log(answer == "yes")

print("Current streak:", guitar.current)
print("Longest streak:", guitar.longest)
Input
yes
yes
yes
no
yes
(an empty line)
Output
Practiced today? yes
Practiced today? yes
Practiced today? yes
Practiced today? no
Practiced today? yes
Practiced today?
Current streak: 1
Longest streak: 3

Each call of log keeps both numbers up to date: a day of practice makes the current streak one longer, and the longest one too if it was just beaten, and a day off starts the current streak again from 0. The main program only reads the answers and hands each one to the object. answer == "yes" is already True or False, just what log expects.

Your task

Challenge 1 of 3

This class has two mistakes, both in how it calls its own methods. Fix them, so that the program prints:

Ann 42 70 True
Bo 0 0 False

Challenge 2

Write a class Expenses that keeps track of money spent. Give it these methods:

  • add(amount) records one expense.
  • count() returns how many expenses have been recorded.
  • total() returns their sum.
  • largest() returns the largest expense, or 0 when there are none.

Decide yourself which attributes the class needs, but don't give an attribute the same name as a method: self.count = 0 would hide the method count. This code:

trip = Expenses()
trip.add(12.5)
trip.add(40)
trip.add(7.5)
print(trip.count(), trip.total(), trip.largest())

prints

3 60.0 40

Challenge 3

Write a class Elevator for a building with the floors 0 to 10. An elevator remembers just two numbers: the floor it is on, in the attribute floor, and how many floors it has traveled in all, in the attribute traveled. A new elevator is on floor 0 and has traveled 0 floors. Give the class two methods:

  • exists(floor), a helper method, returns True when the building has that floor.
  • go_to(floor) moves the elevator to the floor, adds the number of floors it passes to traveled and returns True. From floor 2 up to 7 is 5 floors, and from 7 down to 3 is 4. For a floor that doesn't exist, it changes nothing and returns False: use exists to find out.

Then write the main program. It asks for floors until the user types an empty line, prints No such floor when the elevator can't go to the floor, and at the end tells where the elevator is and how far it went, as in this example:

Floor: 4
Floor: 12
No such floor
Floor: 1
Floor:
Now on floor 1, traveled 7 floors