OOP design of code representing units of measurement I am developing a meta-data framework for monitoring systems such that I can easily create data adapters that can pull in data from different systems. One key component is the unit of measurement. To compare datasets which have different units I need to convert them to a common unit. To build adapters I need to have a system capable of describing complicated situations.

Units are just strings that define what the numbers mean e.g. 102 kWh is 102 kiloWatt hours, this can be converted to the SI unit for energy (Joules) by multiplying by a conversion factor (3600000 in this case).

Some data sources have built in conversion systems and provide me with data to a base unit that they specify, e.g. one database will give me energy data measured in various units (one may be called 'kWh*0.01', another 'Btu*1000'). These data will be provided along with a conversion factor to calculate the 'base unit' for that system which is e.g. GJ.

Here is my code to define units

class Unit(object):
    def __init__(self, base_unit, coefficient, name, suffix):
        self.name = name
        self.suffix = suffix
        self.base_unit = base_unit
        self.coefficient = float(coefficient)

    def to_base(self, value):   
        return value * self.coefficient

    def from_base(self, value):
        return value / self.coefficient

    def to_unit(self, value, unit):
        if unit.base_unit != self.base_unit:
            raise TypeError, "Units derived from %s and %s are incompatible." % (self.base_unit, unit.base_unit)
        return unit.from_base(self.to_base(value))

    def __repr__(self):
        return "%s (%s)" % (self.name, self.suffix)


class BaseUnit(Unit):
    def __init__(self, name, suffix):
        super(BaseUnit, self).__init__(self, 1.0, name, suffix)


I use these classes to represent the unit information pulled from the source database. In this case, I pass in a record from the 'units' table and use the appropriate fields to construct a unit with the given description and abbreviation that can then be converted into the base unit provided by the system (GJ for energy, M3 for water).

def convert_unit(unit):
    """Convert a provided unit record into my unit"""
    desc = unit.description.strip()
    suffix = unit.abbreviation.strip()
    if unit.type == 'energy':
        base = BaseUnit("GigaJoules", "GJ")
        return Unit(base, float(unit.units_per_gj), desc, suffix)
    elif unit.type == 'water':
        base = BaseUnit("Cubic metres", "m3")
        return Unit(base, float(unit.units_per_m3), desc, suffix)
    else:
        return BaseUnit(desc, suffix)


When designing my classes I had in the back of my mind an idea to enforce a common base unit (e.g. Joules) for energy across multiple data sources, I think I can do this by adding another tier and simply making the current base unit a unit with a given base (may require hard coding the conversion factor between my base unit and GJ).

def convert_unit(unit):
    """Convert a provided unit record into my unit"""
    desc = unit.description.strip()
    suffix = unit.abbreviation.strip()
    if unit.type == 'energy':
        base = BaseUnit("Joules", "J")
        source_base = Unit(base, 1000000000, "GigaJoules", "GJ")
        return Unit(source_base, float(unit.units_per_gj), desc, suffix)
    elif unit.type == 'water':
        base = BaseUnit("Cubic metres", "m3")
        return Unit(base, float(unit.units_per_m3), desc, suffix)
    else:
        return BaseUnit(desc, suffix)


The to_base, from_base and to_unit methods are intended for use with numpy arrays of measurements. I typically use the unit classes inside a dataset class that holds data as a numpy array and a unit instance.

How does this look? It feels neat to me. Are there any style issues?
raise TypeError, "Units derived from %s and %s are incompatible." % (self.base_unit)


The python style guide recommends raising exceptions in the raise TypeError(message) form. TypeError is also questionable as the correct exception to throw here. The error here doesn't seem to be quite the same as making inappropriate use of python types. I'd have a UnitError class.

def __repr__(self):            
     return "%s (%s)" % (self.name, self.suffix)


__repr__ is supposed to return either a valid python expression or something of the form &lt;text&gt;

base = BaseUnit("Cubic metres", "m3") 


I'd make a global cubic meter object

My overall thought is: do you really need this? My inclination would be to convert all data to  the correct units as I read it. That way all data in my program would be using consistent units and I wouldn't have to worry about whether data is in some other exotic unit. Of course, depending on your exact scenario, that may not be a reasonable approach.