We're going to implement a class, BoundedInt, that only allows arithmetic within a given range of integers. For simplicity, we're only going to support addition and subtraction for now.
Here's the standard class template, which we'll go over in more detail later on. The test program is here:
https://gogs.gtsplab.com:5432/j/python-test/src/main/test13.py
You can either append it to the template below, or save the template as BoundedInt.py in the same directory and run test13.py to execute the tests.
class BoundedInt():
def __init__(self, i, min, max):
"""
Create a bounded integer.
Args:
i (int): The initial value of the integer.
min (int): The minimum value of the integer.
max (int): The maximum value of the integer.
"""
self.i = i
self.min = min
self.max = max
1. Read this: https://docs.python.org/3/reference/datamodel.html?highlight=int#object.int
2. Modify the template to pass test 1, which checks that int() called on your class will return the value of i.
class BoundedInt():
def __init__(self, i, min, max):
"""
Create a bounded integer.
Args:
i (int): The initial value of the integer.
min (int): The minimum value of the integer.
max (int): The maximum value of the integer.
"""
self.i = max if i > max else min if i < min else i
self.min = min
self.max = max
It's the first draft I have made to meet the requirement; however, the result returns to be "❌ Test 1 failed!", so I want to check i to see what happends to this result and the modified BoundedInt is shown as:
class BoundedInt():
def __init__(self, i, min, max):
"""
Create a bounded integer.
Args:
i (int): The initial value of the integer.
min (int): The minimum value of the integer.
max (int): The maximum value of the integer.
"""
'''
self.i = max if i > max else min if i < min else i
self.min = min
self.max = max
'''
if i < min or i > max:
raise ValueError(f"Initial value {i} is outside the range [{min}, {max}]")
self.i = i
self.min = min
self.max = max
def __int__(self):
return self.i
The result after runnning this modified script says "Initial value 3735928559 is outside the range [10, 0]". If we change the range defined in BoundedIntTester to be [0, 1000000000000], then it will return "✅ Test 1 passed!".
3. Read this, and the paragraph below it: https://docs.python.org/3/reference/datamodel.html?highlight=int#object.add
4. What do you think the types of x and y are? Guess, without checking first. What are they, actually? You can use the debugger's local window to find out.
Before checking, I guess x and y are integers because they are used as objects in arithmetic operations. However, I check their types in debugger's local window with the following sample and I find that they can be objects of any type, as long as they implement the __add__() method.
class Data:
def __init__(self, value):
self.value = value
def __add__(self, other):
return Data(self.value + other.value)
a = Data('a')
b = Data('b')
c = a + b
print(c.value)
5. Don't forget that you can access the value of a BoundedInt by using either .i on it, or int(), now that you've passed test 1.
6. Modify the template to pass test 2, which checks that:
* Your class implements the + operator.
* It throws ValueError when attempting to add another BoundedInt that would exceed self.max.
To implement the addition operation, an __add__() method should be included here in the template.
class BoundedInt():
def __init__(self, i, min, max):
"""
Create a bounded integer.
Args:
i (int): The initial value of the integer.
min (int): The minimum value of the integer.
max (int): The maximum value of the integer.
"""
self.i = min if i < min else (max if i > max else i)
self.min = min
self.max = max
def __int__(self):
return self.i
def __add__(self, other):
result = self.i + other.i
if result > self.max:
raise ValueError("Result exceeds maximum value")
return BoundedInt(result, self.min, self.max)
7. Do the same as 6, but for subtraction, to pass test 3.
Similarly, we just use the subtraction operator __sub__() in the BoundedInt class.
class BoundedInt():
def __init__(self, i, min, max):
self.i = min if i < min else (max if i > max else i)
self.min = min
self.max = max
def __int__(self):
return self.i
def __sub__(self, other):
res = self.i - other.i
if res < self.min:
raise ValueError(f"Result {res} is less than the minimum value {self.min}")
return BoundedInt(res, self.min, self.max)
Furthermore, to pass test 3, the range of bi5 needs to be change as the initial value 2 is outside the range [0, 1]. For instance, if we change the range to be [0, 4], test 3 will pass.
class BoundedIntTester:
def __init__(self):
pass
def test3(self):
"""
Test that addition works for two BoundedInts, and throws an error if the bounds are exceeded.
"""
bi4 = BoundedInt(1, min = 0, max = 1)
bi5 = BoundedInt(2, min = 0, max = 4)
try:
bi6 = bi4 - bi5 # 1 - 2 = (-1), which is less than min = 0
print(f"❌ Test 3 failed!")
except ValueError as err:
print(f"✅ Test 3 passed!")
tester = BoundedIntTester()
tester.test3()
8. Modify the template so it also supports adding a regular integer to a BoundedInt, to pass test 4. Note that you can use isinstance() to determine the class of a variable.
class BoundedInt():
def __init__(self, i, min, max):
if i < min or i > max:
raise ValueError(f"Initial value {i} is outside the range [{min}, {max}]")
self.i = i
self.min = min
self.max = max
def __add__(self, other):
if isinstance(other, BoundedInt):
result = self.i + other.i
if result < self.min or result > self.max:
raise ValueError(f"Result {result} is out of bounds [{self.min}, {self.max}]")
return BoundedInt(result, self.min, self.max)
else:
return BoundedInt(self.i + other, self.min, self.max)
9. Same as 8, but for subtraction.
class BoundedInt():
def __init__(self, i, min, max):
if i < min or i > max:
raise ValueError(f"Initial value {i} is outside the range [{min}, {max}]")
self.i = i
self.min = min
self.max = max
def __sub__(self, other):
if isinstance(other, int):
if self.i - other < self.min:
raise ValueError("ValueError: The result exceeds the minimum limit.")
return BoundedInt(self.i - other, self.min, self.max)
elif isinstance(other, BoundedInt):
if self.i - other.i < self.min:
raise ValueError("ValueError: The result exceeds the minimum limit.")
return BoundedInt(self.i - other.i, self.min, self.max)
10. You don't have to implement the code, but think about how you would change the template so it could allow adding a BoundedInt to an emoji, if there was a hypothetical function sentiment(emoji) which returned a floating point number from -10.0 to 10.0 which indicated whether the emoji was happy or sad. e.g. BoundedInt(1, min = 1, max = 10) + 😁 would raise ValueError.
I think initially we need to check if the other operand is an emoji and if it is, to call the hypothetical function and raise an error if the result is out of bounds. Here, I just draft a script to try to do this task with comments for each step. I didn't try to run it but hope it works.
def __add__(self, other):
if isinstance(other, BoundedInt):
# If the other operand is a BoundedInt, call the existing __add__ method
return BoundedInt(self.i + other.i, min=self.min, max=self.max)
elif hasattr(other, 'sentiment') and callable(other.sentiment):
# hasattr() method returns true if an object has the given named attribute and false if it does not
# I am not sure whether I use it correctly here or I should use hasattr(x, "__call__")
# If the other operand has a sentiment function, call it
sentiment_val = other.sentiment()
if self.min <= self.i + sentiment_val <= self.max:
# If the result is within bounds, create a new BoundedInt
return BoundedInt(self.i + sentiment_val, min=self.min, max=self.max)
else:
# Otherwise, raise a ValueError
raise ValueError("Resulting value is outside of bounds.")
else:
# If the other operand is neither a BoundedInt nor has a sentiment function, raise a TypeError
raise TypeError("Unsupported operand type for +: 'BoundedInt' and '{}'".format(type(other)))