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))
A 10 km ride costs 24
34Traceback (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()
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.)