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())
1:30:09
1:40:09
1 40The 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)
9950
00259950 + 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.