diff options
author | Hernan Grecco <hernan.grecco@gmail.com> | 2014-03-02 03:53:45 -0300 |
---|---|---|
committer | Hernan Grecco <hernan.grecco@gmail.com> | 2014-03-02 03:53:45 -0300 |
commit | d9ace7996374cfe0bfd23ae122c34ab8fcea2196 (patch) | |
tree | afd05993b4439551e990a23b2ec8baa070f32a4f | |
parent | 72ff82545a89f62dfdb7114abd71decc4dd01384 (diff) | |
parent | 96bcb223421581c85ee36906ca450b3b7fb8e2c9 (diff) | |
download | pint-d9ace7996374cfe0bfd23ae122c34ab8fcea2196.tar.gz |
Fixed conflicts with rec-issue-107 and merged
-rw-r--r-- | CHANGES | 6 | ||||
-rw-r--r-- | docs/faq.rst | 2 | ||||
-rw-r--r-- | pint/__init__.py | 6 | ||||
-rw-r--r-- | pint/formatter.py | 182 | ||||
-rw-r--r-- | pint/quantity.py | 5 | ||||
-rw-r--r-- | pint/testsuite/__init__.py | 10 | ||||
-rw-r--r-- | pint/testsuite/test_quantity.py | 4 | ||||
-rw-r--r-- | pint/unit.py | 34 | ||||
-rw-r--r-- | pint/util.py | 71 |
9 files changed, 215 insertions, 105 deletions
@@ -5,6 +5,12 @@ Pint Changelog 0.5 (unreleased) ---------------- +- Nothing changed yet. + + +0.4.2 (2014-02-14) +------------------ + - Python 2.6 support (Issue #96, thanks tiagocoutinho) - Fixed symbol for inch. diff --git a/docs/faq.rst b/docs/faq.rst index f163de4..08bfe74 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -25,4 +25,6 @@ You mention other similar Python libraries. Can you point me to those? `Units <https://bitbucket.org/adonohue/units/>`_ +`udunitspy <https://github.com/blazetopher/udunitspy>`_ + If your are aware of another one, please let me know. diff --git a/pint/__init__.py b/pint/__init__.py index d91d9c1..049a7d6 100644 --- a/pint/__init__.py +++ b/pint/__init__.py @@ -15,8 +15,10 @@ from __future__ import with_statement import os import subprocess import pkg_resources +from .formatter import formatter from .unit import UnitRegistry, DimensionalityError, UndefinedUnitError -from .util import formatter, pi_theorem, logger +from .util import pi_theorem, logger + from .context import Context _DEFAULT_REGISTRY = UnitRegistry() @@ -31,7 +33,7 @@ except: # on any error just try to grab the version that is installed on the sy __version__ = pkg_resources.get_distribution('pint').version except: pass # we seem to have a local copy without any repository control or installed without setuptools - # so the reported version will be __unknown__ + # so the reported version will be __unknown__ def _build_quantity(value, units): return _DEFAULT_REGISTRY.Quantity(value, units) diff --git a/pint/formatter.py b/pint/formatter.py new file mode 100644 index 0000000..859a4d9 --- /dev/null +++ b/pint/formatter.py @@ -0,0 +1,182 @@ +# -*- coding: utf-8 -*- +""" + pint.formatter + ~~~~~~~~~ + + Format units for pint. + + :copyright: 2013 by Pint Authors, see AUTHORS for more details. + :license: BSD, see LICENSE for more details. +""" + +from __future__ import division, unicode_literals, print_function, absolute_import + +import re + +__JOIN_REG_EXP = re.compile("\{\d*\}") + +def _join(fmt, iterable): + if not iter: + return '' + if not __JOIN_REG_EXP.search(fmt): + return fmt.join(iterable) + miter = iter(iterable) + first = next(miter) + for val in miter: + ret = fmt.format(first, val) + first = ret + return first + +_PRETTY_EXPONENTS = '⁰¹²³⁴⁵⁶⁷⁸⁹' + +def _pretty_fmt_exponent(num): + ret = '{0:n}'.format(num).replace('-', '⁻') + for n in range(10): + ret = ret.replace(str(n), _PRETTY_EXPONENTS[n]) + return ret + +# _FORMATS maps format specifications to the corresponding argument set to +# formatter(). +_FORMATS = { + 'P': { # Pretty format. + 'as_ratio': True, + 'single_denominator': False, + 'product_fmt': '·', + 'division_fmt': '/', + 'power_fmt': '{0}{1}', + 'parentheses_fmt': '({0})', + 'exp_call': _pretty_fmt_exponent, + }, + + 'L': { # Latex print format. + 'as_ratio': True, + 'single_denominator': True, + 'product_fmt': r' \cdot ', + 'division_fmt': r'\frac[{0}][{1}]', + 'power_fmt': '{0}^[{1}]', + 'parentheses_fmt': '{0}^[{1}]', + }, + + 'H': { # Latex format. + 'as_ratio': True, + 'single_denominator': True, + 'product_fmt': r' ', + 'division_fmt': r'{0}/{1}', + 'power_fmt': '{0}<sup>{1}</sup>', + 'parentheses_fmt': r'({0})', + }, + + '': { # Default format. + 'as_ratio': True, + 'single_denominator': False, + 'product_fmt': ' * ', + 'division_fmt': ' / ', + 'power_fmt': '{0} ** {1}', + 'parentheses_fmt': r'({0})', + }, + + 'C': { # Compact format. + 'as_ratio': True, + 'single_denominator': False, + 'product_fmt': '*', # TODO: Should this just be ''? + 'division_fmt': '/', + 'power_fmt': '{0}**{1}', + 'parentheses_fmt': r'({0})', + }, + } + +def formatter(items, as_ratio=True, single_denominator=False, + product_fmt=' * ', division_fmt=' / ', power_fmt='{0} ** {1}', + parentheses_fmt='({0})', exp_call=lambda x: '{0:n}'.format(x)): + """Format a list of (name, exponent) pairs. + + :param items: a list of (name, exponent) pairs. + :param as_ratio: True to display as ratio, False as negative powers. + :param single_denominator: all with terms with negative exponents are + collected together. + :param product_fmt: the format used for multiplication. + :param division_fmt: the format used for division. + :param power_fmt: the format used for exponentiation. + :param parentheses_fmt: the format used for parenthesis. + + :return: the formula as a string. + """ + if as_ratio: + fun = lambda x: exp_call(abs(x)) + else: + fun = exp_call + + pos_terms, neg_terms = [], [] + + for key, value in sorted(items): + if value == 1: + pos_terms.append(key) + elif value > 0: + pos_terms.append(power_fmt.format(key, fun(value))) + elif value == -1: + neg_terms.append(key) + else: + neg_terms.append(power_fmt.format(key, fun(value))) + + if pos_terms: + pos_ret = _join(product_fmt, pos_terms) + elif as_ratio and neg_terms: + pos_ret = '1' + else: + pos_ret = '' + + if not neg_terms: + return pos_ret + + if as_ratio: + if single_denominator: + neg_ret = _join(product_fmt, neg_terms) + if len(neg_terms) > 1: + neg_ret = parentheses_fmt.format(neg_ret) + else: + neg_ret = _join(division_fmt, neg_terms) + else: + neg_ret = product_fmt.join(neg_terms) + + return _join(division_fmt, [pos_ret, neg_ret]) + +# Extract just the type from the specification mini-langage: see +# http://docs.python.org/2/library/string.html#format-specification-mini-language + +_BASIC_TYPES = frozenset('bcdeEfFgGnosxX%') +_KNOWN_TYPES = frozenset(_FORMATS.keys()) + +def _parse_spec(spec): + result = '' + for ch in reversed(spec): + if ch == '~' or ch in _BASIC_TYPES: + continue + elif ch in _KNOWN_TYPES: + if result: + raise ValueError("expected ':' after format specifier") + else: + result = ch + elif ch.isalpha(): + raise ValueError("Unknown conversion specified " + ch) + else: + break + return result + +def format_unit(unit, spec): + if not unit: + return 'dimensionless' + spec = _parse_spec(spec) + fmt = _FORMATS.get(spec) + if not fmt: + raise ValueError('Unknown conversion specifier ' + spec) + + result = formatter(unit.items(), **fmt) + if spec == 'L': + result = result.replace('[', '{').replace(']', '}') + return result + +def remove_custom_flags(spec): + for flag in _FORMATS.keys(): + if flag: + spec = spec.replace(flag, '') + return spec diff --git a/pint/quantity.py b/pint/quantity.py index f42b756..924f74b 100644 --- a/pint/quantity.py +++ b/pint/quantity.py @@ -14,6 +14,7 @@ import operator import functools from collections import Iterable +from .formatter import remove_custom_flags from .unit import DimensionalityError, UnitsContainer, UnitDefinition, UndefinedUnitError from .compat import string_types, ndarray, np, _to_magnitude @@ -144,8 +145,8 @@ class _Quantity(object): else: units = self.units - return format(self.magnitude, spec.replace('L', '').replace('P', '').replace('H', '')) \ - + ' ' + format(units, spec) + return '%s %s' % (format(self.magnitude, remove_custom_flags(spec)), + format(units, spec)) # IPython related code def _repr_html_(self): diff --git a/pint/testsuite/__init__.py b/pint/testsuite/__init__.py index 2710797..528ead7 100644 --- a/pint/testsuite/__init__.py +++ b/pint/testsuite/__init__.py @@ -75,8 +75,9 @@ class TestCase(unittest.TestCase): val = abs((second - first) / (second + first)) self.assertLess(val, rel, msg=msg) + def testsuite(): - """A testsuite that has all the pyflim tests. + """A testsuite that has all the pint tests. """ return unittest.TestLoader().discover(os.path.dirname(__file__)) @@ -87,3 +88,10 @@ def main(): unittest.main() except Exception as e: print('Error: %s' % e) + + +def run(): + """Run all tests i + """ + test_runner = unittest.TextTestRunner() + test_runner.run(testsuite()) diff --git a/pint/testsuite/test_quantity.py b/pint/testsuite/test_quantity.py index d948cad..088ea4f 100644 --- a/pint/testsuite/test_quantity.py +++ b/pint/testsuite/test_quantity.py @@ -133,10 +133,12 @@ class TestQuantity(TestCase): ('{0:L}', r'4.12345678 \frac{kilogram \cdot meter^{2}}{second}'), ('{0:P}', '4.12345678 kilogram·meter²/second'), ('{0:H}', '4.12345678 kilogram meter<sup>2</sup>/second'), + ('{0:C}', '4.12345678 kilogram*meter**2/second'), ('{0:~}', '4.12345678 kg * m ** 2 / s'), ('{0:L~}', r'4.12345678 \frac{kg \cdot m^{2}}{s}'), ('{0:P~}', '4.12345678 kg·m²/s'), ('{0:H~}', '4.12345678 kg m<sup>2</sup>/s'), + ('{0:C~}', '4.12345678 kg*m**2/s'), ): self.assertEqual(spec.format(x), result) @@ -146,10 +148,12 @@ class TestQuantity(TestCase): for spec, result in (('L', r'4.12345678 \frac{kilogram \cdot meter^{2}}{second}'), ('P', '4.12345678 kilogram·meter²/second'), ('H', '4.12345678 kilogram meter<sup>2</sup>/second'), + ('C', '4.12345678 kilogram*meter**2/second'), ('~', '4.12345678 kg * m ** 2 / s'), ('L~', r'4.12345678 \frac{kg \cdot m^{2}}{s}'), ('P~', '4.12345678 kg·m²/s'), ('H~', '4.12345678 kg m<sup>2</sup>/s'), + ('C~', '4.12345678 kg*m**2/s'), ): ureg.default_format = spec self.assertEqual('{0}'.format(x), result) diff --git a/pint/unit.py b/pint/unit.py index d45209a..c4fa928 100644 --- a/pint/unit.py +++ b/pint/unit.py @@ -24,9 +24,10 @@ from numbers import Number from tokenize import untokenize, NUMBER, STRING, NAME, OP from .context import Context, ContextChain, _freeze -from .util import (formatter, logger, pi_theorem, solve_dependencies, ParserHelper, +from .util import (logger, pi_theorem, solve_dependencies, ParserHelper, string_preprocessor, find_connected_nodes, find_shortest_path) from .compat import tokenizer, string_types, NUMERIC_TYPES, TransformDict +from .formatter import format_unit class UndefinedUnitError(ValueError): @@ -298,39 +299,14 @@ class UnitsContainer(dict): return dict.__eq__(self, other) def __str__(self): - if not self: - return 'dimensionless' - return formatter(self.items()) + return self.__format__('') def __repr__(self): tmp = '{%s}' % ', '.join(["'{0}': {1}".format(key, value) for key, value in sorted(self.items())]) return '<UnitsContainer({0})>'.format(tmp) def __format__(self, spec): - if 'L' in spec: - tmp = formatter(self.items(), True, True, - r' \cdot ', r'\frac[{0}][{1}]', '{0}^[{1}]', - r'\left( {0} \right)') - tmp = tmp.replace('[', '{').replace(']', '}') - return tmp - elif 'P' in spec: - def fmt_exponent(num): - PRETTY = '⁰¹²³⁴⁵⁶⁷⁸⁹' - ret = '{0:n}'.format(num).replace('-', '⁻') - for n in range(10): - ret = ret.replace(str(n), PRETTY[n]) - return ret - tmp = formatter(self.items(), True, False, - '·', '/', '{0}{1}', - '({0})', fmt_exponent) - return tmp - elif 'H' in spec: - tmp = formatter(self.items(), True, True, - r' ', r'{0}/{1}', '{0}<sup>{1}</sup>', - r'({0})') - return tmp - else: - return str(self) + return format_unit(self, spec) def __copy__(self): ret = self.__class__() @@ -956,7 +932,7 @@ class UnitRegistry(object): # must first convert to Decimal before we can '*' the values if isinstance(value, Decimal): return Decimal(str(factor)) * value - + return factor * value def pi_theorem(self, quantities): diff --git a/pint/util.py b/pint/util.py index 1730006..f8ffd15 100644 --- a/pint/util.py +++ b/pint/util.py @@ -113,77 +113,6 @@ def column_echelon_form(matrix, ntype=Fraction, transpose_result=False): return _transpose(M), _transpose(I), swapped -__JOIN_REG_EXP = re.compile("\{\d*\}") - -def _join(fmt, iterable): - if not iter: - return '' - if not __JOIN_REG_EXP.search(fmt): - return fmt.join(iterable) - miter = iter(iterable) - first = next(miter) - for val in miter: - ret = fmt.format(first, val) - first = ret - return first - - -def formatter(items, as_ratio=True, single_denominator=False, - product_fmt=' * ', division_fmt=' / ', power_fmt='{0} ** {1}', - parentheses_fmt='({0})', exp_call=lambda x: '{0:n}'.format(x)): - """Format a list of (name, exponent) pairs. - - :param items: a list of (name, exponent) pairs. - :param as_ratio: True to display as ratio, False as negative powers. - :param single_denominator: all with terms with negative exponents are - collected together. - :param product_fmt: the format used for multiplication. - :param division_fmt: the format used for division. - :param power_fmt: the format used for exponentiation. - :param parentheses_fmt: the format used for parenthesis. - - :return: the formula as a string. - """ - if as_ratio: - fun = lambda x: exp_call(abs(x)) - else: - fun = exp_call - - pos_terms, neg_terms = [], [] - - for key, value in sorted(items): - if value == 1: - pos_terms.append(key) - elif value > 0: - pos_terms.append(power_fmt.format(key, fun(value))) - elif value == -1: - neg_terms.append(key) - else: - neg_terms.append(power_fmt.format(key, fun(value))) - - if pos_terms: - pos_ret = _join(product_fmt, pos_terms) - elif as_ratio and neg_terms: - pos_ret = '1' - else: - pos_ret = '' - - if not neg_terms: - return pos_ret - - if as_ratio: - if single_denominator: - neg_ret = _join(product_fmt, neg_terms) - if len(neg_terms) > 1: - neg_ret = parentheses_fmt.format(neg_ret) - else: - neg_ret = _join(division_fmt, neg_terms) - else: - neg_ret = product_fmt.join(neg_terms) - - return _join(division_fmt, [pos_ret, neg_ret]) - - def pi_theorem(quantities, registry=None): """Builds dimensionless quantities using the Buckingham π theorem |