How do you combine custom objects in Python to create a collection that maintains the interface of the underlying objects?

Viewed 98

I have a group of objects in python I'd like to sum without losing access to the functionality implemented in the objects.

To illustrate the point, consider this class:

class PlaneTicket:
    def __init__(self, origin:str, destination:str):
        ...

    def price(self, pricingDate:date)->float:
        '''Returns the best price available on a given date'''
        ...

In my application I need to be able to sum these objects. You can think of it as having to create a journey that requires two plane tickets.

flight1 = PlaneTicket('london', 'new-york')
flight2 = PlaneTicket('new-york', 'paris')

journey = flight1 + flight2

Now, the interesting thing is, I also want to be able to use the methods in the underlying objects. Eg.

journey.price('2021-06-19') 
# Should equal sum(flight.price('2021-06-19') for flight in journey.flights)

So, I could implement a class Journey and make the sum of PlaneTicket objects a Journey object, and also then implement a .price on the Journey class.

However, I may be missing a better solution as I'm sure this is a common problem. Moreover, I'd need different implementations of the Journey class if I'm summing, averaging, or multiplying the PlaneTicket objects.

I suppose the general formulation would be:

  1. I have a collection of objects that implement a method foo()
  2. I want to aggregate these objects (eg. summing them)
  3. I want to be able to call foo on the aggregation and have the return values of the constituent objects aggregated.
1 Answers

You could implement a custom __add__ method like my example below.

class PlaneTicket:
    def __init__(self, origin: str, destination: str, price: int):
        self.origin = origin
        self.destination = destination
        self.price = price

    def get_price(self):
        return self.price

    def __add__(self, other):
        assert self.destination == other.origin
        new_ticket = PlaneTicket(self.origin, other.destination, self.price + other.price)
        return new_ticket


flight1 = PlaneTicket("london", "new-york", 1000)
flight2 = PlaneTicket("new-york", "paris", 2000)

journey = flight1 + flight2
print(f"Flying from {journey.origin} to {journey.destination} costs {journey.get_price()}")

Output

Flying from london to paris costs 3000
Related