unit-syntax 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {unit_syntax-0.2.0 → unit_syntax-0.2.2}/PKG-INFO +26 -22
- {unit_syntax-0.2.0 → unit_syntax-0.2.2}/README.md +25 -21
- {unit_syntax-0.2.0 → unit_syntax-0.2.2}/pyproject.toml +1 -1
- unit_syntax-0.2.2/unit_syntax/__init__.py +14 -0
- unit_syntax-0.2.2/unit_syntax/ipython.py +71 -0
- unit_syntax-0.2.2/unit_syntax/parser.py +10383 -0
- unit_syntax-0.2.2/unit_syntax/transform.py +41 -0
- unit_syntax-0.2.0/unit_syntax/__init__.py +0 -68
- unit_syntax-0.2.0/unit_syntax/parser.py +0 -5298
- unit_syntax-0.2.0/unit_syntax/transform.py +0 -139
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: unit-syntax
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Physical unit literals for Jupyter and IPython
|
|
5
5
|
Home-page: https://github.com/ahupp/unit-syntax
|
|
6
6
|
Author: Adam Hupp
|
|
@@ -25,11 +25,11 @@ Description-Content-Type: text/markdown
|
|
|
25
25
|
10 meter
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Why? I often use Python as an interactive calculator for physical problems
|
|
28
|
+
Why? I often use Python as an interactive calculator for physical problems and wished it had the type safety of explicit units along with the readability of normal notation.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
`unit-syntax` currently supports Jupyter notebooks and the IPython interpreter; maybe someday support for standalone scripts.
|
|
31
31
|
|
|
32
|
-
How
|
|
32
|
+
[How does it work?](#how-does-it-work)
|
|
33
33
|
|
|
34
34
|
## Getting Started
|
|
35
35
|
|
|
@@ -42,8 +42,7 @@ $ pip install unit-syntax
|
|
|
42
42
|
To enable unit-syntax in a Jupyter/IPython session run:
|
|
43
43
|
|
|
44
44
|
```python
|
|
45
|
-
|
|
46
|
-
unit_syntax.enable_ipython()
|
|
45
|
+
%load_ext unit_syntax
|
|
47
46
|
```
|
|
48
47
|
|
|
49
48
|
Tip: In Jupyter this must be run in its own cell before any units expressions are evaluated.
|
|
@@ -60,13 +59,13 @@ Units can be applied to any "simple" expression:
|
|
|
60
59
|
- unary operators: `-x dBm`
|
|
61
60
|
- power: `x**2 meters`
|
|
62
61
|
|
|
63
|
-
|
|
62
|
+
In expressions mixing units and binary operators, parenthesize:
|
|
64
63
|
|
|
65
64
|
```python
|
|
66
65
|
one_lux = (1 lumen)/(1 meter**2)
|
|
67
66
|
```
|
|
68
67
|
|
|
69
|
-
Units can be used in
|
|
68
|
+
Units can be used in any place where Python allows expressions, e.g:
|
|
70
69
|
|
|
71
70
|
- function arguments: `area_of_circle(radius=1 meter)`
|
|
72
71
|
- list comprehensions: `[x meters for x in range(10)]`
|
|
@@ -92,26 +91,32 @@ x (newton meters)/(second*kg)
|
|
|
92
91
|
x newton meters/(second*kg)
|
|
93
92
|
```
|
|
94
93
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
The rule for applying units only to "simple" expressions rather than treating it as a typical operator is to avoid unintentional error. Imagine units were instead parsed as operator with high precedence and you wrote this reasonable looking expression:
|
|
94
|
+
Using invalid units produces a syntax error at import time:
|
|
98
95
|
|
|
99
96
|
```python
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
>>> 1 smoot
|
|
98
|
+
...
|
|
99
|
+
SyntaxError: 'smoot' is not defined in the unit registry
|
|
102
100
|
```
|
|
103
101
|
|
|
104
|
-
|
|
102
|
+
## How does it work?
|
|
105
103
|
|
|
106
|
-
|
|
104
|
+
The parser is [pegen](https://we-like-parsers.github.io/pegen/), which is the same parser generator used by Python itself. The grammar is a lightly modified version the official Python grammar.
|
|
107
105
|
|
|
108
|
-
|
|
106
|
+
Syntax transformation in IPython/Jupyter uses [IPython custom input transformers](https://ipython.readthedocs.io/en/stable/config/inputtransforms.html).
|
|
109
107
|
|
|
110
|
-
|
|
108
|
+
## Why only allow units on simple expressions?
|
|
111
109
|
|
|
112
|
-
|
|
110
|
+
Imagine units were instead parsed as operator with high precedence and you wrote this reasonable looking expression:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
ppi = 300 pixels/inch
|
|
114
|
+
y = x inches * ppi
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`inches * 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 in general it's too error prone to allow free intermixing of operators and units. (Note: This is not a hypothical concern, I hit this within 10 minutes of first trying out the idea)
|
|
113
118
|
|
|
114
|
-
|
|
119
|
+
It's easy to detect units used when they aren't allowed (that syntax isn't valid anywhere else), but not generally possible to determine if you forgot parens or meant to write a unit.
|
|
115
120
|
|
|
116
121
|
## Prior Art
|
|
117
122
|
|
|
@@ -127,7 +132,7 @@ A [long discussion on the python-ideas mailing list](https://lwn.net/Articles/90
|
|
|
127
132
|
|
|
128
133
|
To regenerate the parser:
|
|
129
134
|
|
|
130
|
-
`python -m pegen
|
|
135
|
+
`python -m pegen python_units.gram -o unit_syntax/parser.py`
|
|
131
136
|
|
|
132
137
|
Running tests:
|
|
133
138
|
|
|
@@ -140,11 +145,10 @@ $ poetry run pytest
|
|
|
140
145
|
|
|
141
146
|
- Test against various ipython and python versions
|
|
142
147
|
- Support standalone scripts through sys.meta_path
|
|
143
|
-
- Check units at parse time
|
|
144
148
|
- Unit type hints, maybe checked with [@runtime_checkable](https://docs.python.org/3/library/typing.html#typing.runtime_checkable). More Pint typechecking [discussion](https://github.com/hgrecco/pint/issues/1166)
|
|
145
149
|
- Expand the demo Colab notebook
|
|
146
150
|
- Typography of output
|
|
147
151
|
- make it work with numba
|
|
148
152
|
- understand how numpy interop works
|
|
149
|
-
-
|
|
153
|
+
- pre-parse units
|
|
150
154
|
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
10 meter
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Why? I often use Python as an interactive calculator for physical problems
|
|
9
|
+
Why? I often use Python as an interactive calculator for physical problems and wished it had the type safety of explicit units along with the readability of normal notation.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
`unit-syntax` currently supports Jupyter notebooks and the IPython interpreter; maybe someday support for standalone scripts.
|
|
12
12
|
|
|
13
|
-
How
|
|
13
|
+
[How does it work?](#how-does-it-work)
|
|
14
14
|
|
|
15
15
|
## Getting Started
|
|
16
16
|
|
|
@@ -23,8 +23,7 @@ $ pip install unit-syntax
|
|
|
23
23
|
To enable unit-syntax in a Jupyter/IPython session run:
|
|
24
24
|
|
|
25
25
|
```python
|
|
26
|
-
|
|
27
|
-
unit_syntax.enable_ipython()
|
|
26
|
+
%load_ext unit_syntax
|
|
28
27
|
```
|
|
29
28
|
|
|
30
29
|
Tip: In Jupyter this must be run in its own cell before any units expressions are evaluated.
|
|
@@ -41,13 +40,13 @@ Units can be applied to any "simple" expression:
|
|
|
41
40
|
- unary operators: `-x dBm`
|
|
42
41
|
- power: `x**2 meters`
|
|
43
42
|
|
|
44
|
-
|
|
43
|
+
In expressions mixing units and binary operators, parenthesize:
|
|
45
44
|
|
|
46
45
|
```python
|
|
47
46
|
one_lux = (1 lumen)/(1 meter**2)
|
|
48
47
|
```
|
|
49
48
|
|
|
50
|
-
Units can be used in
|
|
49
|
+
Units can be used in any place where Python allows expressions, e.g:
|
|
51
50
|
|
|
52
51
|
- function arguments: `area_of_circle(radius=1 meter)`
|
|
53
52
|
- list comprehensions: `[x meters for x in range(10)]`
|
|
@@ -73,26 +72,32 @@ x (newton meters)/(second*kg)
|
|
|
73
72
|
x newton meters/(second*kg)
|
|
74
73
|
```
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
The rule for applying units only to "simple" expressions rather than treating it as a typical operator is to avoid unintentional error. Imagine units were instead parsed as operator with high precedence and you wrote this reasonable looking expression:
|
|
75
|
+
Using invalid units produces a syntax error at import time:
|
|
79
76
|
|
|
80
77
|
```python
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
>>> 1 smoot
|
|
79
|
+
...
|
|
80
|
+
SyntaxError: 'smoot' is not defined in the unit registry
|
|
83
81
|
```
|
|
84
82
|
|
|
85
|
-
|
|
83
|
+
## How does it work?
|
|
86
84
|
|
|
87
|
-
|
|
85
|
+
The parser is [pegen](https://we-like-parsers.github.io/pegen/), which is the same parser generator used by Python itself. The grammar is a lightly modified version the official Python grammar.
|
|
88
86
|
|
|
89
|
-
|
|
87
|
+
Syntax transformation in IPython/Jupyter uses [IPython custom input transformers](https://ipython.readthedocs.io/en/stable/config/inputtransforms.html).
|
|
90
88
|
|
|
91
|
-
|
|
89
|
+
## Why only allow units on simple expressions?
|
|
92
90
|
|
|
93
|
-
|
|
91
|
+
Imagine units were instead parsed as operator with high precedence and you wrote this reasonable looking expression:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
ppi = 300 pixels/inch
|
|
95
|
+
y = x inches * ppi
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`inches * 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 in general it's too error prone to allow free intermixing of operators and units. (Note: This is not a hypothical concern, I hit this within 10 minutes of first trying out the idea)
|
|
94
99
|
|
|
95
|
-
|
|
100
|
+
It's easy to detect units used when they aren't allowed (that syntax isn't valid anywhere else), but not generally possible to determine if you forgot parens or meant to write a unit.
|
|
96
101
|
|
|
97
102
|
## Prior Art
|
|
98
103
|
|
|
@@ -108,7 +113,7 @@ A [long discussion on the python-ideas mailing list](https://lwn.net/Articles/90
|
|
|
108
113
|
|
|
109
114
|
To regenerate the parser:
|
|
110
115
|
|
|
111
|
-
`python -m pegen
|
|
116
|
+
`python -m pegen python_units.gram -o unit_syntax/parser.py`
|
|
112
117
|
|
|
113
118
|
Running tests:
|
|
114
119
|
|
|
@@ -121,10 +126,9 @@ $ poetry run pytest
|
|
|
121
126
|
|
|
122
127
|
- Test against various ipython and python versions
|
|
123
128
|
- Support standalone scripts through sys.meta_path
|
|
124
|
-
- Check units at parse time
|
|
125
129
|
- Unit type hints, maybe checked with [@runtime_checkable](https://docs.python.org/3/library/typing.html#typing.runtime_checkable). More Pint typechecking [discussion](https://github.com/hgrecco/pint/issues/1166)
|
|
126
130
|
- Expand the demo Colab notebook
|
|
127
131
|
- Typography of output
|
|
128
132
|
- make it work with numba
|
|
129
133
|
- understand how numpy interop works
|
|
130
|
-
-
|
|
134
|
+
- pre-parse units
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import pint
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Use the _DEFAULT_REGISTRY so we can interoperate with other pint-using libraries
|
|
5
|
+
ureg = pint._DEFAULT_REGISTRY
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
BOOTSTRAP = """
|
|
9
|
+
import unit_syntax
|
|
10
|
+
_unit_syntax_q = unit_syntax.ureg.Quantity
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
from .ipython import load_ipython_extension, unload_ipython_extension
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from .transform import transform
|
|
2
|
+
import pint
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _add_formatters(ipython):
|
|
6
|
+
# Overide default formatters to use reduced units. This lets us keep using the
|
|
7
|
+
# default registry for interop with other pint-using libraries, but gives more
|
|
8
|
+
# sensible output in the notebook.
|
|
9
|
+
def add_formatter(mime, display_fn_name):
|
|
10
|
+
fn = getattr(pint.Quantity, display_fn_name, None)
|
|
11
|
+
if fn is None:
|
|
12
|
+
return
|
|
13
|
+
|
|
14
|
+
def fmt_reduced(q, *args):
|
|
15
|
+
return fn(q.to_reduced_units(), *args)
|
|
16
|
+
|
|
17
|
+
formatter = ipython.display_formatter.formatters[mime]
|
|
18
|
+
formatter.for_type(pint.Quantity, fmt_reduced)
|
|
19
|
+
|
|
20
|
+
add_formatter("text/markdown", "_repr_markdown_")
|
|
21
|
+
add_formatter("text/html", "_repr_html_")
|
|
22
|
+
add_formatter("text/plain", "_repr_pretty_")
|
|
23
|
+
add_formatter("text/latex", "_repr_latex_")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_has_init_log = False
|
|
27
|
+
_debug = False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def transform_lines(lines: list[str]) -> list[str]:
|
|
31
|
+
"""IPython transforms provide a list of strings in the current cell, but to parse correctly we
|
|
32
|
+
need to parse them as a single string"""
|
|
33
|
+
return transform("".join(lines)).splitlines(keepends=True)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def ipython_transform(lines):
|
|
37
|
+
ret = transform_lines(lines)
|
|
38
|
+
|
|
39
|
+
if _debug:
|
|
40
|
+
import logging
|
|
41
|
+
|
|
42
|
+
global _has_init_log
|
|
43
|
+
if not _has_init_log:
|
|
44
|
+
logging.basicConfig(level=logging.DEBUG, force=True)
|
|
45
|
+
_has_init_log = True
|
|
46
|
+
logging.debug("unit_syntax: %s -> %s", "\n".join(lines), "\n".join(ret))
|
|
47
|
+
return ret
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def load_ipython_extension(ipython):
|
|
51
|
+
if not hasattr(ipython, "input_transformers_post"):
|
|
52
|
+
raise ImportError("Unsupported IPython version, version >=7 is required")
|
|
53
|
+
|
|
54
|
+
ipython.input_transformers_post.append(ipython_transform)
|
|
55
|
+
|
|
56
|
+
_add_formatters(ipython)
|
|
57
|
+
|
|
58
|
+
from . import BOOTSTRAP
|
|
59
|
+
|
|
60
|
+
ipython.run_cell(BOOTSTRAP)
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
import matplotlib
|
|
64
|
+
|
|
65
|
+
pint.setup_matplotlib(True)
|
|
66
|
+
except ImportError:
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def unload_ipython_extension(ipython):
|
|
71
|
+
ipython.input_transformers_post.remove(ipython_transform)
|