Tell me more ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

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?

share|improve this question

1 Answer

up vote 2 down vote accepted
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 <text>

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.

share|improve this answer
Thanks Winston, I think I need it primarily because I don't know what units the raw data will be in. This code is part of – Graeme Stuart Sep 20 '12 at 15:14
A system for converting the data into the required units. The required units are not always known in advance. I may want to define a custom unit (e.g. The 'cup of tea') for a particular task. – Graeme Stuart Sep 20 '12 at 15:23
Re global units. Would these be best as separate constants in the module namespace or a single dict or set of units? – Graeme Stuart Sep 20 '12 at 15:29
@GraemeStuart, I'd make them constants in a module namespace. I might make a module just for my standard units. – Winston Ewert Sep 21 '12 at 18:43

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.