Quantities and Units in Python

The following code generates a `pint.unit.DimensionalityError` exception with the following message: `pint.unit.DimensionalityError: Cannot convert from 'volt' ([length] ** 2 * [mass] / [current] / [time] ** 3) to 'ohm' ([length] ** 2 * [mass] / [current] ** 2 / [time] ** 3)`. The units of measure are incompatible and you cannot `sum` volts to ohms because Pint cannot perform a conversion from volts to ohms. This way, Pint makes sure you don't make mistakes by mixing incompatible units of measure.

`sum = ((10 * ur.volts) + (500 * ur.ohms))`

If you want to try the same example I used to described the desired behavior for a language incorporated unit of measure, you can enter the following line and Pint will also raise an exception because it cannot convert volts to ohms. In this case, the code mixes three incompatible units: volts, ohms, and inches.

```# Code equivalent to the supposed language incorporated unit of measure
# sum = (10 <volts> + 500 <ohms>) <inches>
sum = ((10 * ur.volts) + (500 * ur.ohms)).to(ur.inches)```

Pint also allows you to work with more complex units. For example, the following code specifies a speed in miles per minute and then converts it to miles per hour:

```speed1 = 2 * ur.miles / ur.minute
miles_per_hour = speed1.to(ur.miles / ur.hour)```

You can also perform operations in compatible complex units. For example, the following code assigns 2 miles per minute to `speed1` and 100 kilometers per hour to `speed2`. Then, the `total_speed` variable holds a `Quantity` instance with the result of adding the two `speed` values.

```speed1 = 2 * ur.miles / ur.minute
speed2 = 100 * ur.kilometers / ur.hour
total_speed = speed1 + speed2
```

In this case, the unit of measure associated with `total_speed` is miles per minute (`mile / minute` for Pint) because Pint converts the other value to the unit associated with the first variable (`speed1`). You have to be careful with this precedence of the operators because if you change the last line with the following line, the unit of measure associated with `total_speed` is going to be kilometers per hour (`kilometer / hour` for Pint) because the first variable is `speed2`:

`total_speed = speed2 + speed1`

Thus, if you want to be sure of the unit of measure that the resulting `Quantity` object will use, it is convenient to specify it, as in the next line:

`total_speed = (speed2 + speed1).to(ur.miles / ur.minute)`

There are many other ways to create a `Quantity` instance with a value and an associated unit of measure. The following lines are equivalent and use the `Quantity` constructor instead of multiplication. The `Quantity` constructor can parse both units and values.

```speed1 = ur.Quantity(2, ur.miles / ur.minute)
speed1 = ur.Quantity(2, 'miles / minute')
speed1 = ur.Quantity('2 * miles / minute')
```

Each time you are defining a new value with an associated unit of measure, you are creating a new instance of `Quantity`; therefore, Pint introduces a performance overhead compared with working with just numeric values. However, Pint provides the necessary features to avoid the most common mistakes related to working with values expressed in different units of measure and extremely powerful customization options. In addition, the unit conversion features allow you to simplify your code and focus on your problem domain because the most common units are already defined with good accuracy.

Units: A Different Approach

Units is another Python package that allows you to work with numerical values associated to units of measure. Unit provides unit objects that you use to create `Quantity` instances that save the magnitude and the associated units for any numerical type. You can perform arithmetic operations between compatible units. When you try to perform arithmetic operations on magnitudes that have incompatible units of measure, Unit raises a `units.exception.IncompatibleUnitsError` exception indicating that it cannot perform the operation when the units are different. However, Units doesn't allow you to work with prefixes, so it doesn't provide a complete set of unit conversion capabilities.

Units is easy to use and includes many common units already defined without the possibility of adding prefixes. You just need to add the following lines to import `unit` and` predefined` from `units` and call the `define_units` method that initializes the most common units included in the package. You can check the predefined units by checking the source code of `predefined.py`.

```from units import unit, predefined
units.predefined.define_units()
```

Then, to assign a unit of measure to a value, use the `unit` object with the desired units of measure and pass the desired value as a parameter. Units will create an instance of the `Quantity` class. The `num` property will hold the specified value and the `unit` property will include the units of measure.

You can read the following line of code as "assign 500 ohms to `r1`."

`r1 = unit('Ohm')(500)`

If you call `print(r1)` the result will be `500.00 Ohm`. If you enter `r1` in the Python Console, the result will be:

```Quantity(500, NamedComposedUnit('Ohm',
ComposedUnit([NamedComposedUnit('V',
ComposedUnit([NamedComposedUnit('W',
ComposedUnit([NamedComposedUnit('J',
ComposedUnit([NamedComposedUnit('N',
ComposedUnit([LeafUnit('m', True),
NamedComposedUnit('kg', ComposedUnit([LeafUnit('g',
True)], [], 1000), False)], [LeafUnit('s', True), LeafUnit('s',
True)], 1), True), LeafUnit('m', True)], [], 1), True)],
[LeafUnit('s', True)], 1), True)], [LeafUnit('A', True)], 1),
True)], [LeafUnit('A', True)], 1), True)).```

As you can see, the way in which Units stores the composed unit is a bit complex, but you can easily check the unit with the help of the `unit` object. For example, the following expression will be `True`:

`r1.unit == unit('Ohm')`

Because Units doesn't define a `kilo-Ohm` unit, if you want to be able to convert from Ohms to kilo-ohms, you need to create the `kilo-Ohm` unit as a scalar multiple of the existing `Ohm` unit. The following code creates the new unit and displays the value of `r1` in `kilo-Ohm`:

```from units import scaled_unit
kiloOhm = scaled_unit('kilo-Ohm', 'Ohm', 1000)
print(kiloOhm(r1))
```

The following line displays the value of `r1` expressed in ohms, no matter the unit in which `r1` has the value saved.

`print(unit('Ohm')(r1))`

The following code uses Units to sum the values of `r1` and `r2`. The `r1_plus_r2` variable holds the result of the sum operation expressed in `ohms` and `r1_plus_r2_kiloOhm` holds the result converted to the new unit defined in the previous code, `kilo-Ohm`. I don't use kilo-ohms just to follow the way Units works with the predefined units, without plurals. Units performs the necessary conversion to allow you to sum the values of `r1` and `r2`.

```ohm = unit('Ohm')
r1 = ohm(500)
r2 = kiloOhm(5.2)
r1_plus_r2 = r1 + r2
r1_plus_r2_kiloOhm = kiloOhm(r1_plus_r2)
```

The following code uses Units to declare four `distance` values expressed in four different units of measure: meters, miles, centimeters and feet.

```distance1 = unit('m')(2500) # meters
distance2 = unit('mi')(2) # miles
distance3 = unit('cm')(3000) # centimeters
distance4 = unit('ft')(3500) # feet
```

However, if you try to `sum` the four distances with the following line, Units will raise a `units.exception.IncompatibleUnitsError` because the four units are different.

`total_distance = distance1 + distance2 + distance3 + distance4`

Conclusion

Of the packages discussed in this article, Numericalunits is very simple, but it doesn't avoid the most common problems. Pint is definitely the most complete package when you just want to focus on the problem domain and take advantage of the prefixes, plurals, alias names, and the simple customization of the units definition file. Units provides basic features that you can improve by defining your own units. If you have to write an algorithm that involves complex formulas with different units of measure in Python, you will definitely want to consider one of these packages before reinventing the wheel.

Gaston Hillar is a frequent contributor to Dr. Dobb's.

More Insights

 To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.