unit-syntax adds support for physical units to the Python language:
>>> speed = 5 meters/second
>>> (2 seconds) * speed
10 meterWhy? I like to use Python as a calculator for physical problems and wished it had the type safety of explicit units along with the readability of normal notation.
unit-syntax works in Jupyter notebooks, standalone Python scripts, and Python packages.
Install the package:
$ pip install unit-syntaxTo enable unit-syntax in a Jupyter/IPython session run:
%load_ext unit_syntaxTip: In Jupyter this must be run in its own cell before any units expressions are evaluated.
To run a standalone script with units:
$ python -m unit_syntax <path_to_script.py>
Note that this installs a custom import hook that affects all imports performed by the script.
To use/distribute a package with unit-syntax, add this in your __init__.py:
from unit_syntax.import_hook import enable_units_for_package
enable_units_for_package(__name__)This applies the transform only to sub-modules of your package.
An interactive notebook to play around with units
Units can be applied to any "simple" expression:
- number: 
1 meter - variables: 
x parsec,y.z watts,area[id] meters**2 - lists and tuples: 
[1., 37.] newton meters - unary operators: 
-x dBm - power: 
x**2 meters 
In expressions mixing units and binary operators, parenthesize:
one_lux = (1 lumen)/(1 meter**2)Units can be used in any place where Python allows expressions, e.g:
- function arguments: 
area_of_circle(radius=1 meter) - list comprehensions: 
[x meters for x in range(10)] 
Quantities can be converted to another measurement system:
>>> (88 miles / hour) furlongs / fortnight
236543.5269120001 furlong / fortnight
>>> (0 degC) degF
31.999999999999936 degree_FahrenheitCompound units (e.g. newtons/meter**2) are supported and follow the usual precedence rules.
Units may not begin with parentheses (consider the possible
interpretations of x (meters)). Parentheses are allowed anywhere else:
# parsed as a function call, will result in a runtime error
x (newton meters)/(second*kg)
# a-ok
x newton meters/(second*kg)Using unknown units produces a syntax error at import time:
>>> 1 smoot
...
SyntaxError: 'smoot' is not defined in the unit registryunit-syntax transforms python-with-units into standard python that calls the excellent pint units handling library.
The parser is pegen, which is a standalone version of the same parser generator used by Python itself. The grammar is a lightly modified version the official Python grammar shipped with pegen.
Syntax transformation in IPython/Jupyter uses IPython custom input transformers.
Syntax transformation of arbitrary Python modules uses importlib's MetaPathFinder, see import-transforms and unit_syntax.import_hook for details.
Imagine units were instead parsed as operator with high precedence and you wrote this reasonable looking expression:
ppi = 300 pixels/inch
y = x inches * ppiinches * ppi would be parsed as the unit, leading to (at best) a runtime error sometime later and at worst an incorrect calculation. This could be avoided by parenthesizing the expression (e.g. (x inches) * ppi, but if that's optional it's easy to forget. So the intent of this restriction is to make these risky forms uncommon and thus more obvious. This is not a hypothetical concern, I hit this within 10 minutes of first using the initial syntax.
The immediate inspriration of unit-syntax is a language called Fortress from Sun Microsystems. Fortress was intended as a modern Fortran, and had first-class support for units in both the syntax and type system.
F# (an OCaml derivative from Microsoft) also has first class support for units.
The Julia package Unitful.jl
A long discussion on the python-ideas mailing list about literal units in Python.
To regenerate the parser:
python -m pegen python_units.gram -o unit_syntax/parser.py
Running tests:
$ poetry install --with dev
$ poetry run pytest
- Test against various ipython and python versions
 - Ensure bytecode caching still works
 - Test with wider range of source files with the wildcard loader
 - Unit type hints, maybe checked with @runtime_checkable. More Pint typechecking discussion
 - Typography of output
 - pre-parse units
 - talk to pint about interop between UnitRegistries
 - Fix reported location of SyntaxError when paren missing