diff options
author | Hernan Grecco <hernan.grecco@gmail.com> | 2013-10-27 01:39:23 -0300 |
---|---|---|
committer | Hernan Grecco <hernan.grecco@gmail.com> | 2013-10-27 01:39:23 -0300 |
commit | 918880b7744f97c5eb24772595d95b3c9b1b5963 (patch) | |
tree | eb86caf0f30484edd30e734ffc256e76e97a20a3 | |
parent | e0db38c64228ccde23745f399cabb6f7907874ef (diff) | |
download | pint-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.py | 12 | ||||
-rw-r--r-- | pint/testsuite/test_contexts.py | 68 | ||||
-rw-r--r-- | pint/testsuite/test_unit.py | 8 | ||||
-rw-r--r-- | pint/unit.py | 25 |
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. |