0

I'm trying to replicate the API of a Java library for a vehicle routing problem and it's highlighted something I don't understand about Python classes.

A vehicle fleet can be broken down into two basic criteria; the first is the types of vehicle you own (instances of VehicleType to define capacities, running costs etc.) and individual vehicles (Vehicle with different shift patterns etc.).

In my simplified example, I want to define one VehicleType and then a single Vehicle that takes the attributes of the VehicleType passed to the constructor. Following the answer by Lennart here, using __getattr__ works perfectly in a general case. However, imagine that I have a driver of one van out of 50 that has a special qualification to carry something hazardous, so I want to add an extra capacity property on the Vehicle instance. I tried the following:

class VehicleType(object):

    def __init__(self, fixed_cost=0, cost_per_distance=0, cost_per_time=0):
        self.capacities = {}
        self.fixed_cost = fixed_cost
        self.cost_per_distance = cost_per_distance
        self.cost_per_time = cost_per_time

    def add_capacity_dimension(self, dimension_id, dimension_size):
        """ Define a package type and how many of them this vehicle type can carry """
        self.capacities[dimension_id] = dimension_size


class Vehicle(object):

    def __init__(self, vehicle_type=None, identifier=None):
        self._vehicle_type = vehicle_type
        self.identifier = identifier

    def add_capacity_dimension(self, dimension_id, dimension_size):
        self.capacities[dimension_id] = dimension_size

    def __getattr__(self, name):
        return getattr(self._vehicle_type, name) 


if __name__ == '__main__':
    van_type = VehicleType()
    van_type.add_capacity_dimension("general items", 34)

    special_van = Vehicle(vehicle_type=van_type)
    special_van.add_capacity_dimension("hazardous chemicals", 50)

    print("Capacities of special van: {}".format(special_van.capacities))
    print("Capacity of van_type:      {}".format(van_type.capacities)) # Why?

I don't understand why my approach has also affected the capacities of the van_type. Vehicle does not directly inherit from VehicleType and I have defined add_capacity_dimension within the Vehicle class. This also contradicts my understanding of the docs about __getattr__:

Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for self). name is the attribute name.

Can someone please explain why the instance van_type is also affected here?

Community
  • 1
  • 1
roganjosh
  • 12,594
  • 4
  • 29
  • 46
  • What part of this surprises you? Are you surprised that `van_type` has a `capacities`? Are you surprised that it was mutated? – user2357112 Apr 11 '17 at 18:51
  • @user2357112 You are correct, I missed the link from looking too long. `capacities` for `Vehicle` requires `__getattr__` in itself. In that case, is the only way to do this to define `capacities` within `Vehicle` and then have `__getattr__` for the rest of the attributes? – roganjosh Apr 11 '17 at 18:51
  • @roganjosh They **will** match. If they don't, then your MCVE is wrong. – wim Apr 11 '17 at 18:56
  • @wim MCVE is correct, I validated `id()` wrong while trying to write it. Checked again. Thanks guys, sorry for the moment. – roganjosh Apr 11 '17 at 18:57

1 Answers1

2

Vehicle instances don't have a "capacities" attribute.

The self.capacities attribute lookup on the special_van (from within Vehicle.add_capacity_dimension implementation) is resolved not on the Vehicle but on the VehicleType, via the custom __getattr__.

Therefore, special_van.capacities and van_type.capacities are the same dict.

wim
  • 338,267
  • 99
  • 616
  • 750