Examples of Classes (Optional Section)

4.6. Examples of Classes (Optional Section)#

4.6.1. Example: Time Class#

Here’s an example of a simple Time class in Python that represents time in hours, minutes, and seconds [Downey, 2015, Python Software Foundation, 2023]:

class Time:
    def __init__(self, hours=0, minutes=0, seconds=0):
        self.hours = hours
        self.minutes = minutes
        self.seconds = seconds

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

    def add_seconds(self, seconds):
        """
        Adds the specified number of seconds to the time.

        Args:
            seconds (int): The number of seconds to add.
        """
        total_seconds = self.to_seconds() + seconds
        self.hours, remaining_seconds = divmod(total_seconds, 3600)
        self.minutes, self.seconds = divmod(remaining_seconds, 60)

    def to_seconds(self):
        """
        Converts the time to seconds.

        Returns:
            int: The total number of seconds represented by the time.
        """
        return self.hours * 3600 + self.minutes * 60 + self.seconds

In this Time class, we have the following methods:

  1. __init__(self, hours=0, minutes=0, seconds=0): The constructor method that initializes the Time object with the provided hours, minutes, and seconds. If no arguments are provided, it defaults to 0.

  2. __str__(self): A special method that returns a string representation of the Time object in the format “hh:mm:ss”.

  3. add_seconds(self, seconds): A method that allows us to add a specified number of seconds to the current time.

  4. to_seconds(self): A method that converts the Time object into total seconds.

You can create instances of the Time class and use its methods like this:

class Time:
    def __init__(self, hours=0, minutes=0, seconds=0):
        """
        Initializes a Time object with the specified hours, minutes, and seconds.

        Args:
            hours (int): Hours component of the time (default is 0).
            minutes (int): Minutes component of the time (default is 0).
            seconds (int): Seconds component of the time (default is 0).
        """
        self.hours = hours
        self.minutes = minutes
        self.seconds = seconds

    def __str__(self):
        """
        Returns the string representation of the time in HH:MM:SS format.

        Returns:
            str: The formatted time string.
        """
        return f"{self.hours:02d}:{self.minutes:02d}:{self.seconds:02d}"

    def add_seconds(self, seconds):
        """
        Adds the specified number of seconds to the time.

        Args:
            seconds (int): The number of seconds to add.
        """
        total_seconds = self.to_seconds() + seconds
        self.hours, remaining_seconds = divmod(total_seconds, 3600)
        self.minutes, self.seconds = divmod(remaining_seconds, 60)

    def to_seconds(self):
        """
        Converts the time to seconds.

        Returns:
            int: The total number of seconds represented by the time.
        """
        return self.hours * 3600 + self.minutes * 60 + self.seconds


# Create a Time object
time1 = Time(10, 30, 45)

# Print the time
print(time1)  # Output: 10:30:45

# Add 15 seconds to the time
time1.add_seconds(15)
print(time1)  # Output: 10:31:00

# Convert the time to total seconds
total_seconds = time1.to_seconds()
print("Total seconds:", total_seconds)  # Output: Total seconds: 37860
10:30:45
10:31:00
Total seconds: 37860

Let’s add the is_after(t1, t2) function to the Time class to check if one time follows another chronologically. Here’s the updated class with the new function:

class Time:
    def __init__(self, hours=0, minutes=0, seconds=0):
        """
        Initializes a Time object with the specified hours, minutes, and seconds.

        Args:
            hours (int): Hours component of the time (default is 0).
            minutes (int): Minutes component of the time (default is 0).
            seconds (int): Seconds component of the time (default is 0).
        """
        self.hours = hours
        self.minutes = minutes
        self.seconds = seconds

    def __str__(self):
        """
        Returns the string representation of the time in HH:MM:SS format.

        Returns:
            str: The formatted time string.
        """
        return f"{self.hours:02d}:{self.minutes:02d}:{self.seconds:02d}"

    def add_seconds(self, seconds):
        """
        Adds the specified number of seconds to the time.

        Args:
            seconds (int): The number of seconds to add.
        """
        total_seconds = self.to_seconds() + seconds
        self.hours, remaining_seconds = divmod(total_seconds, 3600)
        self.minutes, self.seconds = divmod(remaining_seconds, 60)

    def to_seconds(self):
        """
        Converts the time to seconds.

        Returns:
            int: The total number of seconds represented by the time.
        """
        return self.hours * 3600 + self.minutes * 60 + self.seconds

    def is_after(self, other_time):
        """
        Checks if the current time is after the specified time.

        Args:
            other_time (Time): The other Time object to compare against.

        Returns:
            bool: True if the current time is after the other time, False otherwise.
        """
        return (self.hours, self.minutes, self.seconds) > (other_time.hours, other_time.minutes, other_time.seconds)

    def print_time(self):
        """
        Prints the formatted time.
        """
        print(self.__str__())

# Example usage
time = Time(hours=12, minutes=30, seconds=45)
time.print_time()
12:30:45
# Create Time objects
time1 = Time(10, 30, 45)
time2 = Time(12, 15, 30)

# Check if time1 is after time2
print("Is time1 after time2?", time1.is_after(time2))  # Output: False

# Add 15 seconds to time1
time1.add_seconds(15)

# Check again if time1 is after time2
print("Is time1 after time2?", time1.is_after(time2))  # Output: True
Is time1 after time2? False
Is time1 after time2? False

Now, the Time class includes the is_after() function that returns True if the first time (self) is chronologically after the second time (other_time), and False otherwise.

To create a version of the increment function, we can follow the functional programming style and return a new Time object instead of modifying the parameter in place. This way, the original Time object remains unchanged, and the new object represents the incremented time. Here’s the pure version of the increment function [Downey, 2015, Python Software Foundation, 2023]:

def increment(time, seconds):
    """
    Increments a given time by the specified number of seconds.

    Args:
        time (Time): The original time to be incremented.
        seconds (int): The number of seconds to add.

    Returns:
        Time: A new Time object representing the incremented time.
    """
    new_time = Time()
    new_time.hours = time.hours
    new_time.minutes = time.minutes
    new_time.seconds = time.seconds + seconds

    # Adjust minutes and seconds
    new_time.minutes += new_time.seconds // 60
    new_time.seconds %= 60

    # Adjust hours and minutes
    new_time.hours += new_time.minutes // 60
    new_time.minutes %= 60

    return new_time

With this pure version, you can increment a Time object without modifying it. The original Time object will remain unchanged, and you can use the returned new_time object to work with the incremented time.

Here’s an example of how to use this pure version of increment:

# Create a Time object and set its attributes
start = Time()
start.hours = 9
start.minutes = 45
start.seconds = 0

# Increment the time by 150 seconds
incremented_time = increment(start, 150)

# Print the original and incremented times
start.print_time()  # Output: 09:45:00 (original Time object is unchanged)
incremented_time.print_time()  # Output: 09:47:30 (new incremented Time object)
09:45:00
09:47:30

As you can see, the start object remains unchanged, and the incremented_time object represents the new time after incrementing by 150 seconds. This approach ensures the function is pure, and it follows the functional programming style where possible.

4.6.2. Example: Class of functions#

In Python, functions are not classified as classes themselves. Functions and classes are distinct concepts in Python. A function is a block of reusable code that performs a specific task, while a class is a blueprint for creating objects that encapsulate data and behavior.

However, functions can be defined within a class. These functions are known as methods, and they are associated with the class and its instances. Methods have access to the class’s attributes and can operate on them.

Here’s an example of a class containing some methods (functions defined within the class):

class Calculator:
    def add(self, a, b):
        """
        Adds two numbers.

        Args:
            a (int or float): First operand.
            b (int or float): Second operand.

        Returns:
            int or float: The sum of the two operands.
        """
        return a + b

    def subtract(self, a, b):
        """
        Subtracts the second number from the first.

        Args:
            a (int or float): First operand.
            b (int or float): Second operand.

        Returns:
            int or float: The result of subtraction.
        """
        return a - b

    def multiply(self, a, b):
        """
        Multiplies two numbers.

        Args:
            a (int or float): First operand.
            b (int or float): Second operand.

        Returns:
            int or float: The product of the two operands.
        """
        return a * b

    def divide(self, a, b):
        """
        Divides the first number by the second.

        Args:
            a (int or float): Numerator.
            b (int or float): Denominator.

        Returns:
            int or float: The result of division.
        
        Raises:
            ValueError: If the denominator is zero.
        """
        if b != 0:
            return a / b
        else:
            raise ValueError("Cannot divide by zero.")

# Creating an instance of the Calculator class
my_calculator = Calculator()

# Using the methods of the Calculator class
result_add = my_calculator.add(5, 3)
print(result_add)  # Output: 8

result_divide = my_calculator.divide(10, 2)
print(result_divide)  # Output: 5.0
8
5.0

In this example, we defined a class Calculator with four methods: add, subtract, multiply, and divide. When we create an instance of the Calculator class (my_calculator), we can use its methods to perform mathematical operations.

Here’s another example of a class with methods that represent a basic shopping cart:

class ShoppingCart:
    def __init__(self):
        """
        Initializes an empty shopping cart.
        """
        self.items = []

    def add_item(self, item, price):
        """
        Adds an item with its price to the shopping cart.

        Args:
            item (str): The name of the item.
            price (float): The price of the item.
        """
        self.items.append((item, price))

    def remove_item(self, item):
        """
        Removes an item from the shopping cart.

        Args:
            item (str): The name of the item to be removed.
        """
        self.items = [(i, p) for i, p in self.items if i != item]

    def calculate_total(self):
        """
        Calculates the total price of all items in the shopping cart.

        Returns:
            float: The total price of all items.
        """
        total = sum(price for _, price in self.items)
        return total

    def display_items(self):
        """
        Displays the items and their prices in the shopping cart.
        """
        for item, price in self.items:
            print(f"{item}: ${price:.2f}")

# Creating an instance of the ShoppingCart class
my_cart = ShoppingCart()

# Adding items to the cart
my_cart.add_item("Apples", 2.50)
my_cart.add_item("Bananas", 1.75)
my_cart.add_item("Milk", 3.00)

# Displaying the items in the cart
my_cart.display_items()
# Output:
# Apples: $2.50
# Bananas: $1.75
# Milk: $3.00

# Removing an item from the cart
my_cart.remove_item("Bananas")

# Displaying the items after removal
my_cart.display_items()
# Output:
# Apples: $2.50
# Milk: $3.00

# Calculating the total price of the items in the cart
total_price = my_cart.calculate_total()
print(f"Total: ${total_price:.2f}") # Output: Total: $5.50
Apples: $2.50
Bananas: $1.75
Milk: $3.00
Apples: $2.50
Milk: $3.00
Total: $5.50

In this example, we have a class ShoppingCart with four methods: add_item, remove_item, calculate_total, and display_items. The __init__ method initializes an empty list to store the items and their prices.

You can create an instance of the ShoppingCart class (my_cart) and use its methods to add items, remove items, display the items, and calculate the total price of the items in the cart.

Note

The examples in this section are intended to aid in the understanding of various aspects related to defining and utilizing classes. These illustrative examples are frequently encountered in various textbooks and articles that discuss the concept of class definitions in Python.