When you need to perform calculations with values that have an associated unit of measure, it is very common to make mistakes by mixing different units of measure. It is also common to perform incorrect conversions between the different units that generate wrong results. The latest Python release doesn't allow developers to associate a specific numerical value with a unit of measure. In this article, I look at three Python packages that provide different solutions to this problem and allow you to work with units of measure and perform conversions.
Three Different Packages to Add Units of Measure
The need to associate quantities with units of measure in any programming language is easy to understand, even in the most basic math and physics problems. One of the simplest calculations is to sum two values that have an associated base unit. For example, say that you have two electrical resistance values. One of the values is measured in ohms and the other in kilo-ohms. To sum the values, you must choose the desired unit and convert one of the values to the chosen unit. If you want the result to be expressed in ohms, you must convert the value in kilo-ohms to ohms, sum the two values expressed in ohms, and provide the result in ohms.
The following Python code uses variables with a suffix that defines the specific unit being used in each case. You have probably used or seen similar conventions. The suffixes make the code less error-prone because you easily understand that r1_in_ohms
holds a value in ohms, and r2_in_kohms
holds a value in kilo-ohms. Thus, there is a line that assigns the result of converting the r2_in_kohms
value to ohms to the new r2_in_ohms
variable. The last line calculates the sum and holds the result in ohms because both variables hold values in the same unit of measure.
r1_in_ohms = 500 r2_in_kohms = 5.2 r2_in_ohms = r2_in_kohms * 1e3 r1_plus_r2_in_ohms = r1_in_ohms + r2_in_ohms
Obviously, the code is still error-prone because there won't be any exception or syntax error if a developer adds the following line to sum ohms and kilo-ohms without performing the necessary conversions:
r3_in_ohms = r1_in_ohms + r2_in_kohms
There is no rule that assures that all the variables included in the sum
operation must use the same suffix; that is, the same unit. There aren't invalid operations between variables that hold values with incompatible units. For example, you might sum a voltage
value to a resistance
value and the code won't produce any error warning.
Now, imagine that Python adds support for units of measure. Each numeric value can have an associated unit of measure enclosed within <>. The following three lines would replace the previous code with an easier to understand syntax:
r1 = 500 <ohms> r2 = 5.2 <kilo-ohms> r1_plus_r2 = (r1 + r2) <ohms>
r1
holds a value of 500
and an associated unit of measure, ohms
. r2
holds a value of 5.2
and an associated unit of measure, kilo-ohms
. Because each variable includes information about its unit of measure, the sum
operation is smart and it can convert compatible units such as ohms
and kilo-ohms
. The last line sums the two values taking into account their unit of measure, and converts the result to the specified unit, ohms
. The r1_plus_r2
variable holds the result of the sum
operation expressed in ohms
. The following line would produce an exception because the units of measure are incompatible:
sum = (10 <volts> + 500 <ohms>) <inches>
However, the support should be smart enough to allow you to mix different length units. For example, the following line would produce a valid result in inches.
sum = (10 <inches> + 1200 <centimeters>) <inches>
Python doesn't support units of measure, but the three Python packages I examine here provide different ways to enable them. Each package takes a different approach. While none works as well as a native language feature would, these solutions do provide a baseline that you can improve according to your specific needs.
Package Name |
Latest Version |
Python Package Web Page |
Numericalunits |
1.12 |
|
Pint |
0.2.1 |
|
Units |
0.06 |
Numericalunits: A Bare-Bones Solution
Numericalunits is a single Python module (numericalunits.py) that provides easy unit conversion features for your operations. You just need to follow two simple rules:
- To assign a unit of measure to a value, multiply the value by the unit.
- To express a value generated by its multiplication by the unit in a different unit, divide the value by that unit.
You simply need to add the following lines to import the module with an alias (nu
) and execute the $reset_units
method to start working with the different units.
import numericalunits as nu nu.reset_units('SI')
When you call nu.reset_units('SI')
, Numericalunits uses standard SI units (short for Système Internationale d'Unités in French) for storing the values (see Figure 1). This way, any length value is stored in meters, no matter the length unit you specify in the multiplication. Read here if you want more information about SI base units.
Figure 1: The SI base units and their interdependencies.
If you call the default nu.reset_units()
, Numericalunits uses a random set of units instead of the standard SI units. I really don't like using a random set of units because it usually generates a loss of precision and results that lack accuracy. The only advantage of using random units is that you can check dimensional errors by running calculations twice and comparing whether the results match. You have to call nu.reset_units()
before each calculation and compare the two values. I don't like this way of checking dimension errors because it adds a huge overhead and it is indeed error-prone. Thus, I suggest using Numericalunits as a unit conversion helper with the standard SI units initialization.
Numericalunits doesn't save information about the unit of measure in the numerical variable; therefore, there is no way to know which unit you used when you assigned the value. If you need more than a unit conversions helper, I suggest working with one of the other packages.
You can read the following line of code as "assign 500 ohms to r1
."
r1 = 500 * nu.ohm
You can read the following line of code as "display the value of r1
expressed in kilo-ohms."
print(r1 / nu.kohm)
The following line displays the value of r1
expressed in ohms.
print(r1 / nu.ohm)
The following code uses Numericalunits to sum the values of r1
and r2
. Notice that the code is self-documented because you can easily see that r1
holds a value in ohms
and r2
in kilo-ohms
. The r1_plus_r2
variable holds the result of the sum
operation expressed in ohms
and r1_plus_r2_kohms
holds the result converted to kilo-ohms
. Notice that you can sum the values of r1
and r2
without having to convert the units to ohms and the result will be accurate because of the way in which Numericalunits saves the values in the base units.
import numericalunits as nu nu.reset_units('SI') r1 = 500 * nu.ohm r2 = 5.2 * nu.kohm r1_plus_r2 = r1 + r2 r1_plus_r2_kohms = r1_plus_r2 / nu.kohm
The following code uses Numericalunits to sum four distance
values expressed in four different units of measure: meters, miles, centimeters, and feet. Numericalunits doesn't support plurals for the units. The total_distance
variable holds the total distance expressed in feet.
import numericalunits as nu nu.reset_units('SI') distance1 = 2500 * nu.m distance2 = 2 * nu.mile distance3 = 3000 * nu.cm distance4 = 3500 * nu.foot total_distance = (distance1 + distance2 + distance3 + distance4) / nu.foot
Pint: A Complete Package
Pint is a Python package that allows you to work with numerical values associated to units of measure. Because Pint saves the magnitude and the associated units for any numerical type, you are able to know which unit you used when you assigned the magnitude value. You can perform arithmetic operations between compatible units and convert from and to different units. When you try to perform arithmetic operations on magnitudes that have incompatible units of measure, Pint raises a Pint.unit.DimensionalityError
exception indicating that it cannot convert from one unit to the other before performing the arithmetic operation.
The UnitRegistry
class stores the definitions and relationships between units. By default, Pint uses the default_en.txt
unit definitions file. This file contains the different units and the prefixes that the UnitRegistry
will recognize in plain text. You can easily edit this text file to add any unit you might need to support.