Python Arena
Step 3 of 4

Keeping the object valid

The code that uses an object is called its client. The client asks the object to do things by calling its methods. That gives the methods a chance to make sure that the object's state always makes sense: a stock count is never negative, a volume knob stays between its lowest and highest setting.

Here is a counter that counts down but never goes below zero, not even when it is made with a negative number:

class Countdown:
    def __init__(self, start):
        self.value = 0
        if start > 0:
            self.value = start

    def decrease(self):
        if self.value > 0:
            self.value -= 1


timer = Countdown(2)
for i in range(4):
    timer.decrease()
    print(timer.value)

broken = Countdown(-5)
print(broken.value)
Output
1
0
0
0
0

A method that can't do what was asked can also tell the client so, by returning True or False:

class Stock:
    def __init__(self, item, amount):
        self.item = item
        self.amount = amount

    def take(self, count):
        if count <= self.amount:
            self.amount -= count
            return True
        return False


paint = Stock("paint can", 5)
print(paint.take(3), paint.amount)
print(paint.take(3), paint.amount)
Output
True 2
False 2

The second time, there aren't enough cans, so take changes nothing and returns False. The client can then decide what to do, such as print a message.

When a request makes no sense at all, the method can raise an error, as you learned in part 6:

class Stock:
    def __init__(self, item, amount):
        self.item = item
        self.amount = amount

    def add(self, count):
        if count < 0:
            raise ValueError("count can't be negative")
        self.amount += count


paint = Stock("paint can", 5)
paint.add(4)
print(paint.amount)
paint.add(-10)
Output
9
Python stops with
Traceback (most recent call last):
  File "main.py", line 15, in <module>
    paint.add(-10)
    ~~~~~~~~~^^^^^
  File "main.py", line 8, in add
    raise ValueError("count can't be negative")
ValueError: count can't be negative

Encapsulation

Keeping an object's data valid by handling it only through the object's methods is called encapsulation. The client calls take and add and doesn't need to know how the stock is kept inside the object.

Python doesn't force this, though: a client can still write paint.amount = -10, and nothing stops it. The methods protect the object only when the client uses them. The Encapsulation module in part 9 shows how a class can hide its attributes; until then, change an object's state through its methods.

Your task

Challenge 1 of 3

The volume of this radio should stay between 0 and 10. Fill in the conditions, so that up and down never take it past those limits and the program prints:

10
0

Challenge 2

Write a class Battery. A new battery has the attribute charge set to 100 (percent). Give it two methods:

  • use(amount) uses that much charge and returns True, when the battery has at least that much left. If it doesn't, the charge stays as it was and the method returns False.
  • recharge(amount) adds that much charge, but never more than up to 100.

This code:

phone = Battery()
print(phone.use(70), phone.charge)
print(phone.use(40), phone.charge)
phone.recharge(50)
print(phone.charge)
phone.recharge(50)
print(phone.charge)

prints

True 30
False 30
80
100

Challenge 3

Write a class Thermostat whose constructor takes the target temperature and stores it in the attribute target. When no temperature is given, it is 20. The target must always stay between 5 and 30 degrees, both included. Give the class these methods:

  • warmer() raises the target by one degree, but not above 30.
  • cooler() lowers the target by one degree, but not below 5.
  • set(degrees) sets the target to the given degrees. If they are below 5 or above 30, it changes nothing and raises a ValueError instead.

This code:

home = Thermostat()
home.warmer()
print(home.target)
home.set(30)
home.warmer()
print(home.target)
try:
    home.set(45)
except ValueError:
    print("Not allowed")
print(home.target)

prints

21
30
Not allowed
30