Python Arena
Step 4 of 4

Private methods

Two underscores at the start work for method names too. A private method is a helper: a piece of the class's own work that got a name of its own, to keep the other methods short or to save writing the same lines twice. The client never needs to call it.

A taxi works out a fare in two places: when a customer asks what a ride would cost, and when the ride is driven. The formula is written once, in a private method:

class Taxi:
    def __init__(self):
        self.__earned = 0

    def __fare(self, km):
        return 4 + 2 * km

    def quote(self, km):
        print(f"A {km} km ride costs {self.__fare(km)}")

    def drive(self, km):
        self.__earned += self.__fare(km)

    def earned(self):
        return self.__earned


taxi = Taxi()
taxi.quote(10)
taxi.drive(10)
taxi.drive(3)
print(taxi.earned())
print(taxi.__fare(5))
Output
A 10 km ride costs 24
34
Python stops with
Traceback (most recent call last):
  File "main.py", line 23, in <module>
    print(taxi.__fare(5))
          ^^^^^^^^^^^
AttributeError: 'Taxi' object has no attribute '__fare'

Inside the class, the helper is called like any other method, with self. in front: self.__fare(km). Outside it, the name can't be reached, just as with a private attribute. If the price per kilometer changes, there is one line to fix, and no client has to change.

Public or private?

Ask who the method is for. If it's something a client would want to do with the object, such as quote, drive or earned, make it public. If it only exists so that the class's own code is shorter or clearer, make it private. That keeps the class simple to use (the client sees only what it needs), and you can rename or rewrite a private helper later without breaking anyone's program.

Some helpers are also unsafe in the client's hands, because they only make sense at certain moments. Here the scores are set up by a private method. The constructor uses it, and so does new_game, which first prints the final scores. If __reset were public, a client could wipe the scores in the middle of a game:

class Scoreboard:
    def __init__(self, players):
        self.__players = players
        self.__reset()

    def __reset(self):
        self.__points = {}
        for player in self.__players:
            self.__points[player] = 0

    def score(self, player, points):
        self.__points[player] += points

    def new_game(self):
        print("Final scores:", self.__points)
        self.__reset()


board = Scoreboard(["Ann", "Bo"])
board.score("Ann", 3)
board.score("Bo", 5)
board.new_game()
board.score("Ann", 1)
board.new_game()
Output
Final scores: {'Ann': 3, 'Bo': 5}
Final scores: {'Ann': 1, 'Bo': 0}

Who sees what

Look at the Scoreboard above from three places. Inside score, the names in reach are its parameters player and points, plus everything the object has: self.__points, self.__players, and self.__reset() too. The code at the bottom of the program can call score and new_game, but none of the names with two underscores. And the parameter points exists only while score runs. new_game can't see it, so any other method could have a variable called points of its own without a clash. (It has nothing to do with the attribute self.__points either: an attribute always comes with self. in front.)

Your task

Challenge 1 of 3

A door lock opens with a four-digit code. Fill in the blanks: the private method that checks a code is called __correct, and open uses it. The program should print:

Wrong code
Door open

Challenge 2

A shopping list should treat "Milk", "milk" and " milk " as the same item. Write the class ShoppingList with a private list of items and these methods:

  • a private method __clean(self, item), which returns the item with the spaces at both ends removed and in lowercase;
  • add(self, item), which adds the cleaned item, unless it's already on the list;
  • has(self, item), which returns True if the cleaned item is on the list;
  • __str__(self), which returns the items separated by a comma and a space, in the order they were added.

For example, this code:

groceries = ShoppingList()
groceries.add("Milk")
groceries.add("  eggs ")
groceries.add("MILK")
print(groceries)
print(groceries.has(" Eggs"))
print(groceries.has("bread"))

prints:

milk, eggs
True
False

Challenge 3

A robot moves on a board of 10 by 10 squares, where both x and y go from 0 to 9. It starts at (0, 0). Write the class Robot with:

  • a private method __move(self, dx, dy), which moves the robot by dx steps along x and dy steps along y, but only if it stays on the board. Otherwise the robot stays where it is.
  • four public methods that use __move: north (y goes up by one), south (y goes down by one), east (x goes up by one) and west (x goes down by one).
  • __str__, which returns the position, such as (2, 1).

For example, this code:

robot = Robot()
robot.west()
robot.east()
robot.east()
robot.north()
print(robot)

prints:

(2, 1)