summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHernan Grecco <hernan.grecco@gmail.com>2013-10-27 01:39:23 -0300
committerHernan Grecco <hernan.grecco@gmail.com>2013-10-27 01:39:23 -0300
commit918880b7744f97c5eb24772595d95b3c9b1b5963 (patch)
treeeb86caf0f30484edd30e734ffc256e76e97a20a3
parente0db38c64228ccde23745f399cabb6f7907874ef (diff)
downloadpint-918880b7744f97c5eb24772595d95b3c9b1b5963.tar.gz
Transforming values using contexts defined in strings
- `UnitRegisty.parse_expression` now takes keyword arguments that are used as locals() in eval. Names found in keyword arguments take precedence over units defined in the registry. - The transformation function now takes keyword arguments that are passed to `UnitRegistry.parse_expression`. This keyword arguments are filled with the context values. See Issue #65
-rw-r--r--pint/context.py12
-rw-r--r--pint/testsuite/test_contexts.py68
-rw-r--r--pint/testsuite/test_unit.py8
-rw-r--r--pint/unit.py25
4 files changed, 73 insertions, 40 deletions
diff --git a/pint/context.py b/pint/context.py
index 1a505e2..5d90963 100644
--- a/pint/context.py
+++ b/pint/context.py
@@ -130,7 +130,9 @@ class Context(object):
rel, eq = line.split('=')
names.update(_varname_re.findall(eq))
- func = lambda ureg, value: ureg.parse_expression(eq)
+ def func(ureg, value, **kwargs):
+ return ureg.parse_expression(eq, value=value, **kwargs)
+
if '<->' in rel:
src, dst = (ParserHelper.from_string(s) for s in rel.split('<->'))
ctx.add_transformation(src, dst, func)
@@ -159,11 +161,11 @@ class Context(object):
def __keytransform__(src, dst):
return _freeze(src), _freeze(dst)
- def transform(self, src, dst, value):
+ def transform(self, src, dst, registry, value):
"""Transform a value.
"""
_key = self.__keytransform__(src, dst)
- return self.funcs[_key](value, **self.defaults)
+ return self.funcs[_key](registry, value, **self.defaults)
class ContextChain(ChainMap):
@@ -210,10 +212,10 @@ class ContextChain(ChainMap):
self._graph[fr_].add(to_)
return self._graph
- def transform(self, src, dst, value):
+ def transform(self, src, dst, registry, value):
"""Transform the value, finding the rule in the chained context.
(A rule in last context will take precedence)
:raises: KeyError if the rule is not found.
"""
- return self[(src, dst)].transform(src, dst, value)
+ return self[(src, dst)].transform(src, dst, registry, value)
diff --git a/pint/testsuite/test_contexts.py b/pint/testsuite/test_contexts.py
index e2d46d3..7f47fd1 100644
--- a/pint/testsuite/test_contexts.py
+++ b/pint/testsuite/test_contexts.py
@@ -28,22 +28,18 @@ class TestHandler(BufferingHandler):
def emit(self, record):
self.buffer.append(record.__dict__)
-th = TestHandler()
-logger.addHandler(th)
-
-
def add_ctxs(ureg):
a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[time]': -1})
d = Context('sp')
- d.add_transformation(a, b, lambda x: ureg.speed_of_light / x)
- d.add_transformation(b, a, lambda x: ureg.speed_of_light / x)
+ d.add_transformation(a, b, lambda ureg, x: ureg.speed_of_light / x)
+ d.add_transformation(b, a, lambda ureg, x: ureg.speed_of_light / x)
ureg.add_context(d)
a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[current]': -1})
d = Context('ab')
- d.add_transformation(a, b, lambda x: 1 / x)
- d.add_transformation(b, a, lambda x: 1 / x)
+ d.add_transformation(a, b, lambda ureg, x: 1 / x)
+ d.add_transformation(b, a, lambda ureg, x: 1 / x)
ureg.add_context(d)
@@ -51,15 +47,15 @@ def add_ctxs(ureg):
def add_arg_ctxs(ureg):
a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[time]': -1})
d = Context('sp')
- d.add_transformation(a, b, lambda x, n: ureg.speed_of_light / x / n)
- d.add_transformation(b, a, lambda x, n: ureg.speed_of_light / x / n)
+ d.add_transformation(a, b, lambda ureg, x, n: ureg.speed_of_light / x / n)
+ d.add_transformation(b, a, lambda ureg, x, n: ureg.speed_of_light / x / n)
ureg.add_context(d)
a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[current]': -1})
d = Context('ab')
- d.add_transformation(a, b, lambda x: 1 / x)
- d.add_transformation(b, a, lambda x: 1 / x)
+ d.add_transformation(a, b, lambda ureg, x: 1 / x)
+ d.add_transformation(b, a, lambda ureg, x: 1 / x)
ureg.add_context(d)
@@ -69,15 +65,15 @@ def add_argdef_ctxs(ureg):
d = Context('sp', defaults=dict(n=1))
assert d.defaults == dict(n=1)
- d.add_transformation(a, b, lambda x, n: ureg.speed_of_light / x / n)
- d.add_transformation(b, a, lambda x, n: ureg.speed_of_light / x / n)
+ d.add_transformation(a, b, lambda ureg, x, n: ureg.speed_of_light / x / n)
+ d.add_transformation(b, a, lambda ureg, x, n: ureg.speed_of_light / x / n)
ureg.add_context(d)
a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[current]': -1})
d = Context('ab')
- d.add_transformation(a, b, lambda x: 1 / x)
- d.add_transformation(b, a, lambda x: 1 / x)
+ d.add_transformation(a, b, lambda ureg, x: 1 / x)
+ d.add_transformation(b, a, lambda ureg, x: 1 / x)
ureg.add_context(d)
@@ -87,15 +83,15 @@ def add_sharedargdef_ctxs(ureg):
d = Context('sp', defaults=dict(n=1))
assert d.defaults == dict(n=1)
- d.add_transformation(a, b, lambda x, n: ureg.speed_of_light / x / n)
- d.add_transformation(b, a, lambda x, n: ureg.speed_of_light / x / n)
+ d.add_transformation(a, b, lambda ureg, x, n: ureg.speed_of_light / x / n)
+ d.add_transformation(b, a, lambda ureg, x, n: ureg.speed_of_light / x / n)
ureg.add_context(d)
a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[current]': 1})
d = Context('ab', defaults=dict(n=0))
- d.add_transformation(a, b, lambda x, n: ureg.ampere * ureg.meter * n / x)
- d.add_transformation(b, a, lambda x, n: ureg.ampere * ureg.meter * n / x)
+ d.add_transformation(a, b, lambda ureg, x, n: ureg.ampere * ureg.meter * n / x)
+ d.add_transformation(b, a, lambda ureg, x, n: ureg.ampere * ureg.meter * n / x)
ureg.add_context(d)
@@ -470,59 +466,75 @@ class TestContexts(unittest.TestCase):
with ureg.context('sp', n=6):
self.assertEqual(q.to('Hz'), s / 6)
+ def _test_ctx(self, ctx):
+ ureg = UnitRegistry()
+ q = 500 * ureg.meter
+ s = (ureg.speed_of_light / q).to('Hz')
+ ureg.add_context(ctx)
+ with ureg.context(ctx.name):
+ self.assertEqual(q.to('Hz'), s)
+
def test_simple_from_string(self):
+
a = Context.__keytransform__(UnitsContainer({'[time]': 1}), UnitsContainer({'[length]': -1}))
b = Context.__keytransform__(UnitsContainer({'[length]': 1}), UnitsContainer({'[time]': -1}))
s = """@context spectral
- [length] -> 1 / [time] = 1 / value
- [time] -> 1 / [length] = 1 / value
+ [length] -> 1 / [time] = c / value
+ [time] -> 1 / [length] = c / value
"""
+
c = Context.from_string(s)
self.assertEqual(c.name, 'spectral')
self.assertEqual(c.aliases, ())
self.assertEqual(c.defaults, {})
self.assertEqual(set(c.funcs.keys()), set((a, b)))
+ self._test_ctx(c)
s = """@context spectral = sp
- [time] <-> 1 / [length] = 1 / value
+ [time] <-> 1 / [length] = c / value
"""
c = Context.from_string(s)
self.assertEqual(c.name, 'spectral')
self.assertEqual(c.aliases, ('sp', ))
self.assertEqual(c.defaults, {})
self.assertEqual(set(c.funcs.keys()), set((a, b)))
+ self._test_ctx(c)
s = """@context spectral = sp = spe
- [time] <-> 1 / [length] = 1 / value
+ [time] <-> 1 / [length] = c / value
"""
c = Context.from_string(s)
self.assertEqual(c.name, 'spectral')
self.assertEqual(c.aliases, ('sp', 'spe', ))
self.assertEqual(c.defaults, {})
self.assertEqual(set(c.funcs.keys()), set((a, b)))
+ self._test_ctx(c)
def test_auto_inverse_from_string(self):
+
a = Context.__keytransform__(UnitsContainer({'[time]': 1}), UnitsContainer({'[length]': -1}))
b = Context.__keytransform__(UnitsContainer({'[length]': 1}), UnitsContainer({'[time]': -1}))
s = """@context spectral
- [time] <-> 1 / [length] = 1 / value
+ [time] <-> 1 / [length] = c / value
"""
c = Context.from_string(s)
self.assertEqual(c.defaults, {})
self.assertEqual(set(c.funcs.keys()), set((a, b)))
+ self._test_ctx(c)
def test_definedvar_from_string(self):
a = Context.__keytransform__(UnitsContainer({'[time]': 1}), UnitsContainer({'[length]': -1}))
b = Context.__keytransform__(UnitsContainer({'[length]': 1}), UnitsContainer({'[time]': -1}))
s = """@context spectral
- [time] <-> 1 / [length] = 1 / value
+ [time] <-> 1 / [length] = c / value
"""
c = Context.from_string(s)
self.assertEqual(c.defaults, {})
self.assertEqual(set(c.funcs.keys()), set((a, b)))
+ self._test_ctx(c)
def test_parameterized_from_string(self):
a = Context.__keytransform__(UnitsContainer({'[time]': 1}), UnitsContainer({'[length]': -1}))
@@ -534,6 +546,7 @@ class TestContexts(unittest.TestCase):
c = Context.from_string(s)
self.assertEqual(c.defaults, {'n': 1})
self.assertEqual(set(c.funcs.keys()), set((a, b)))
+ self._test_ctx(c)
# If the variable is not present in the definition, then raise an error
s = """@context(n=1) spectral
@@ -545,6 +558,9 @@ class TestContexts(unittest.TestCase):
ureg = UnitRegistry()
+ th = TestHandler()
+ logger.addHandler(th)
+
add_ctxs(ureg)
d = Context('ab')
diff --git a/pint/testsuite/test_unit.py b/pint/testsuite/test_unit.py
index 9cb4c76..b3ff468 100644
--- a/pint/testsuite/test_unit.py
+++ b/pint/testsuite/test_unit.py
@@ -2,6 +2,7 @@
from __future__ import division, unicode_literals, print_function, absolute_import
+import math
import copy
import unittest
import operator as op
@@ -184,6 +185,13 @@ class TestRegistry(TestCase):
FORCE_NDARRAY = False
+ def test_parse_number(self):
+ self.assertEqual(self.ureg.parse_expression('pi'), math.pi)
+ self.assertEqual(self.ureg.parse_expression('x', x=2), 2)
+ self.assertEqual(self.ureg.parse_expression('x', x=2.3), 2.3)
+ self.assertEqual(self.ureg.parse_expression('x * y', x=2.3, y=3), 2.3 * 3)
+ self.assertEqual(self.ureg.parse_expression('x', x=(1+1j)), (1+1j))
+
def test_parse_single(self):
self.assertEqual(self.ureg.parse_expression('meter'), self.Q_(1, UnitsContainer(meter=1.)))
self.assertEqual(self.ureg.parse_expression('second'), self.Q_(1, UnitsContainer(second=1.)))
diff --git a/pint/unit.py b/pint/unit.py
index 70ad465..d352c73 100644
--- a/pint/unit.py
+++ b/pint/unit.py
@@ -799,7 +799,7 @@ class UnitRegistry(object):
if path:
src = self.Quantity(value, src)
for a, b in zip(path[:-1], path[1:]):
- src = self._active_ctx.transform(a, b, src)
+ src = self._active_ctx.transform(a, b, self, src)
value, src = src.magnitude, src.units
@@ -910,8 +910,11 @@ class UnitRegistry(object):
return ret
- def parse_expression(self, input_string):
+ def parse_expression(self, input_string, **values):
"""Parse a mathematical expression including units and return a quantity object.
+
+ Numerical constants can be specified as keyword arguments and will take precedence
+ over the names defined in the registry.
"""
if not input_string:
@@ -922,10 +925,10 @@ class UnitRegistry(object):
result = []
unknown = set()
for toknum, tokval, _, _, _ in gen:
- if toknum in (STRING, NAME): # replace NUMBER tokens
+ if toknum == NAME:
# TODO: Integrate math better, Replace eval
- if tokval == 'pi':
- result.append((toknum, str(math.pi)))
+ if tokval == 'pi' or tokval in values:
+ result.append((toknum, tokval))
continue
try:
tokval = self.get_name(tokval)
@@ -948,10 +951,14 @@ class UnitRegistry(object):
if unknown:
raise UndefinedUnitError(unknown)
- return eval(untokenize(result), {'__builtins__': None},
- {'REGISTRY': self._units,
- 'Q_': self.Quantity,
- 'U_': UnitsContainer})
+ return eval(untokenize(result),
+ {'__builtins__': None,
+ 'REGISTRY': self._units,
+ 'Q_': self.Quantity,
+ 'U_': UnitsContainer,
+ 'pi': math.pi},
+ values
+ )
def wraps(self, ret, args, strict=True):
"""Wraps a function to become pint-aware.