Python Arena
Step 2 of 4

Values worked out from others

Often some of an object's values follow from others. A length of time in seconds also tells you how many hours and minutes it is. A good rule is to store the basic data in attributes and to work out the rest in methods, so that it is always up to date:

class Duration:
    def __init__(self, seconds):
        self.seconds = seconds

    def hours(self):
        return self.seconds // 3600

    def minutes(self):
        return self.seconds % 3600 // 60

    def add(self, seconds):
        self.seconds += seconds

    def __str__(self):
        return f"{self.hours()}:{self.minutes():02}:{self.seconds % 60:02}"


film = Duration(5409)
print(film)
film.add(600)
print(film)
print(film.hours(), film.minutes())
Output
1:30:09
1:40:09
1 40

The object stores only the total number of seconds. add changes that one attribute, and the hours and minutes are right after it, because they are worked out each time they are asked for.

In an f-string, :02 after a whole number shows it with at least two digits, filling with zeros in front: {9:02} is 09. Clock times need that.

If a class does store a value worked out from others, say to save working it out again and again, then every method that changes the data must update that value too. Forget one, and the object contradicts itself. That is why the rule above is a good one.

Values that wrap around

Some values go around in a circle. An odometer with four digits goes from 9999 back to 0000, as a clock goes from 23:59 to 00:00. The remainder operator % keeps such a value in range:

class Odometer:
    def __init__(self):
        self.km = 0

    def drive(self, km):
        self.km = (self.km + km) % 10000

    def __str__(self):
        return f"{self.km:04}"


old_van = Odometer()
old_van.drive(9950)
print(old_van)
old_van.drive(75)
print(old_van)
Output
9950
0025

9950 + 75 is 10025, and the remainder when that is divided by 10000 is 25. With %, the method can never leave the odometer out of range, whatever the client drives.

Your task

Challenge 1 of 3

What does this program print? Type each line it prints.

Challenge 2

Write a class Record that keeps a team's wins and losses. Its constructor takes the starting numbers of wins and losses, both 0 when not given, and stores them in the attributes wins and losses. Give it these methods:

  • win() and lose() add one to the wins or the losses.
  • games() returns the number of games played.
  • win_percent() returns the share of games won as a whole number of percent (use round), or 0 when no games have been played.
  • __str__ returns the wins, a hyphen, the losses and the percent, as shown below.

This code:

team = Record(6, 3)
print(team, team.games())
team.win()
print(team)
rookies = Record()
print(rookies)

prints

6-3 (67%) 9
7-3 (70%)
0-0 (0%)

Challenge 3

Write a class Countdown for a kitchen timer. Its constructor takes the minutes and the seconds to start from. Give it these methods:

  • tick() takes one second off the time. When the time is already 00:00, it stays there.
  • __str__ returns the time left as minutes and seconds, both with two digits: 02:05.

This code:

timer = Countdown(1, 1)
print(timer)
timer.tick()
print(timer)
timer.tick()
print(timer)

prints

01:01
01:00
00:59