Repository: hgrecco/pint

Issue:
Unit.__lt__ makes pprint crash
The vast majority of the implementations of (dis)equality methods in Python (``__lt__`` etc.) raise TypeError when a comparison doesn't make sense. Although this isn't explicitly mandated by the data model, it is a de facto standard.

pint, however, can raise DimensionalityError, which is a subclass of ValueError.
This breaks pprint.pformat, which tries to sort sets and frozensets beyond a certain size and graciously handles TypeError, but not arbitrary exceptions. pprint.pformat is invoked by unittest.TestCase.assertEqual when a comparison fails.

```python
import pprint
import pint

ureg = pint.UnitRegistry()
meter_units = ureg.get_compatible_units(ureg.meter)
hertz_units = ureg.get_compatible_units(ureg.hertz)
pprint.pformat(meter_units | hertz_units)
```
Output:
```
Traceback (most recent call last):
  File "lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 3326, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-26-eecee46faec1>", line 1, in <module>
    pprint.pformat(units)
  File "lib/python3.6/pprint.py", line 58, in pformat
    compact=compact).pformat(object)
  File "lib/python3.6/pprint.py", line 144, in pformat
    self._format(object, sio, 0, 0, {}, 0)
  File "lib/python3.6/pprint.py", line 167, in _format
    p(self, object, stream, indent, allowance, context, level + 1)
  File "lib/python3.6/pprint.py", line 236, in _pprint_set
    object = sorted(object, key=_safe_key)
  File "lib/python3.6/pprint.py", line 89, in __lt__
    return self.obj < other.obj
  File "pint/unit.py", line 219, in <lambda>
    __lt__ = lambda self, other: self.compare(other, op=operator.lt)
  File "pint/unit.py", line 215, in compare
    return self_q.compare(self._REGISTRY.Quantity(1, other), op)
  File "pint/quantity.py", line 78, in wrapped
    result = f(self, *args, **kwargs)
  File "pint/quantity.py", line 1274, in compare
    self.dimensionality, other.dimensionality)
pint.errors.DimensionalityError: Cannot convert from 'x_unit_Cu' ([length]) to 'becquerel' (1 / [time])
pprint.pformat(frozenset({Unit("m"), Unit("Hz")}))
```

Discussion:

Patch:
diff --git a/CHANGES b/CHANGES
index 6e56d90..16cc515 100644
--- a/CHANGES
+++ b/CHANGES
@@ -4,7 +4,75 @@ Pint Changelog
 0.10 (unreleased)
 -----------------
 
-- Nothing changed yet.
+- **BREAKING CHANGE**:
+  Fix crash when applying pprint to large sets of Units.
+  DefinitionSyntaxError is now a subclass of SyntaxError (was ValueError).
+  DimensionalityError and OffsetUnitCalculusError are now subclasses of TypeError (was
+  ValueError).
+  (Issue #915, Thanks Guido Imperiale)
+- All Exceptions can now be pickled and can be accessed from the top-level package.
+  (Issue #915, Thanks Guido Imperiale)
+- TODO #913
+- TODO #911
+- Prevent 1500x slowdown when using .to() with a context
+  (Issue #909, Thanks Guido Imperiale)
+- **BREAKING CHANGE**:
+  Drop support for Python < 3.6, numpy < 1.14, and uncertainties < 3.0;
+  if you still need them, please install pint 0.9.
+  Pint now adheres to NEP-29 <https://numpy.org/neps/nep-0029-deprecation_policy.html>
+  as a rolling dependencies version policy.
+  (Issues #908 and #910, Thanks Guido Imperiale)
+- TODO #907
+- TODO #891
+- TODO #890
+- TODO #889
+- TODO #888
+- TODO #884
+- Quantity, Unit, and Measurement are now accessible as top-level classes
+  (pint.Quantity, pint.Unit, pint.Measurement) and can be
+  instantiated without explicitly creating a UnitRegistry
+  (Issue #880, Thanks Guido Imperiale)
+- Contexts don't need to have a name anymore
+  (Issue #870, Thanks Guido Imperiale)
+- "Board feet" unit added top default registry
+  (Issue #869, Thanks Guido Imperiale)
+- New syntax to add aliases to already existing definitions
+  (Issue #868, Thanks Guido Imperiale)
+- copy.deepcopy() can now copy a UnitRegistry
+  (Issues #864 and #877, Thanks Guido Imperiale)
+- Enabled many tests in test_issues when numpy is not available
+  (Issue #863, Thanks Guido Imperiale)
+- Document the '_' symbols found in the definitions files
+  (Issue #862, Thanks Guido Imperiale)
+- TODO #846
+- TODO #837
+- TODO #835
+- TODO #834
+- TODO #830
+- TODO #829
+- TODO #825
+- TODO #824
+- TODO #822
+- TODO #813
+- TODO #811
+- TODO #809
+- TODO #805
+- TODO #802
+- TODO #798
+- TODO #796
+- TODO #791
+- TODO #788
+- TODO #781
+- TODO #776
+- TODO #784
+- TODO #773
+- TODO #768
+- TODO #767
+- TODO #762
+- TODO #759
+- TODO #756
+- TODO #754
+- TODO #680
 
 
 0.9 (2019-01-12)
diff --git a/pint/__init__.py b/pint/__init__.py
index 55486b2..22c9830 100644
--- a/pint/__init__.py
+++ b/pint/__init__.py
@@ -16,8 +16,10 @@ import pkg_resources
 
 from .context import Context
 from .errors import (
+    DefinitionSyntaxError,
     DimensionalityError,
     OffsetUnitCalculusError,
+    RedefinitionError,
     UndefinedUnitError,
     UnitStrippedWarning
 )
@@ -114,8 +116,10 @@ __all__ = (
     'Unit',
     'UnitRegistry',
 
+    'DefinitionSyntaxError',
     'DimensionalityError',
     'OffsetUnitCalculusError',
+    'RedefinitionError',
     'UndefinedUnitError',
     'UnitStrippedWarning',
 
diff --git a/pint/context.py b/pint/context.py
index 78e763f..c1782b5 100644
--- a/pint/context.py
+++ b/pint/context.py
@@ -115,14 +115,15 @@ class Context:
                     return val.real
                 return val
 
-            _txt = defaults
+            txt = defaults
             try:
                 defaults = (part.split('=') for part in defaults.strip('()').split(','))
-                defaults = dict((str(k).strip(), to_num(v))
-                                for k, v in defaults)
+                defaults = {str(k).strip(): to_num(v) for k, v in defaults}
             except (ValueError, TypeError):
-                raise DefinitionSyntaxError("Could not parse Context definition defaults: '%s'" % _txt,
-                                            lineno=lineno)
+                raise DefinitionSyntaxError(
+                    f"Could not parse Context definition defaults: '{txt}'",
+                    lineno=lineno
+                )
 
             ctx = cls(name, aliases, defaults)
         else:
@@ -160,7 +161,9 @@ class Context:
         if defaults:
             missing_pars = defaults.keys() - set(names)
             if missing_pars:
-                raise DefinitionSyntaxError('Context parameters {} not found in any equation.'.format(missing_pars))
+                raise DefinitionSyntaxError(
+                    f'Context parameters {missing_pars} not found in any equation'
+                )
 
         return ctx
 
diff --git a/pint/errors.py b/pint/errors.py
index 79aae36..d5ae803 100644
--- a/pint/errors.py
+++ b/pint/errors.py
@@ -10,69 +10,81 @@
 """
 
 
-class DefinitionSyntaxError(ValueError):
+def _file_prefix(filename=None, lineno=None):
+    if filename and lineno is not None:
+        return f"While opening {filename}, in line {lineno}: "
+    elif filename:
+        return f"While opening {filename}: "
+    elif lineno is not None:
+        return f"In line {lineno}: "
+    else:
+        return ""
+
+
+class DefinitionSyntaxError(SyntaxError):
     """Raised when a textual definition has a syntax error.
     """
 
-    def __init__(self, msg, filename=None, lineno=None):
-        super().__init__()
-        self.msg = msg
-        self.filename = None
-        self.lineno = None
+    def __init__(self, msg, *, filename=None, lineno=None):
+        super().__init__(msg)
+        self.filename = filename
+        self.lineno = lineno
 
     def __str__(self):
-        mess = "While opening {}, in line {}: "
-        return mess.format(self.filename, self.lineno) + self.msg
+        return _file_prefix(self.filename, self.lineno) + str(self.args[0])
+
+    @property
+    def __dict__(self):
+        # SyntaxError.filename and lineno are special fields that don't appear in
+        # the __dict__. This messes up pickling and deepcopy, as well
+        # as any other Python library that expects sane behaviour.
+        return {"filename": self.filename, "lineno": self.lineno}
+
+    def __reduce__(self):
+        return DefinitionSyntaxError, self.args, self.__dict__
 
 
 class RedefinitionError(ValueError):
     """Raised when a unit or prefix is redefined.
     """
 
-    def __init__(self, name, definition_type):
-        super().__init__()
-        self.name = name
-        self.definition_type = definition_type
-        self.filename = None
-        self.lineno = None
+    def __init__(self, name, definition_type, *, filename=None, lineno=None):
+        super().__init__(name, definition_type)
+        self.filename = filename
+        self.lineno = lineno
 
     def __str__(self):
-        msg = "cannot redefine '{}' ({})".format(self.name,
-                                                   self.definition_type)
-        if self.filename:
-            mess = "While opening {}, in line {}: "
-            return mess.format(self.filename, self.lineno) + msg
-        return msg
+        msg = f"Cannot redefine '{self.args[0]}' ({self.args[1]})"
+        return _file_prefix(self.filename, self.lineno) + msg
+
+    def __reduce__(self):
+        return RedefinitionError, self.args, self.__dict__
 
 
 class UndefinedUnitError(AttributeError):
     """Raised when the units are not defined in the unit registry.
     """
 
-    def __init__(self, unit_names):
-        super().__init__()
-        self.unit_names = unit_names
+    def __init__(self, *unit_names):
+        if len(unit_names) == 1 and not isinstance(unit_names[0], str):
+            unit_names = unit_names[0]
+        super().__init__(*unit_names)
 
     def __str__(self):
-        mess = "'{}' is not defined in the unit registry"
-        mess_plural = "'{}' are not defined in the unit registry"
-        if isinstance(self.unit_names, str):
-            return mess.format(self.unit_names)
-        elif isinstance(self.unit_names, (list, tuple))\
-                and len(self.unit_names) == 1:
-            return mess.format(self.unit_names[0])
-        elif isinstance(self.unit_names, set) and len(self.unit_names) == 1:
-            uname = list(self.unit_names)[0]
-            return mess.format(uname)
-        else:
-            return mess_plural.format(self.unit_names)
+        if len(self.args) == 1:
+            return f"'{self.args[0]}' is not defined in the unit registry"
+        return f"{self.args} are not defined in the unit registry"
 
 
-class DimensionalityError(ValueError):
+class PintTypeError(TypeError):
+    pass
+
+
+class DimensionalityError(PintTypeError):
     """Raised when trying to convert between incompatible units.
     """
 
-    def __init__(self, units1, units2, dim1=None, dim2=None, extra_msg=''):
+    def __init__(self, units1, units2, dim1="", dim2="", *, extra_msg=""):
         super().__init__()
         self.units1 = units1
         self.units2 = units2
@@ -82,32 +94,31 @@ class DimensionalityError(ValueError):
 
     def __str__(self):
         if self.dim1 or self.dim2:
-            dim1 = ' ({})'.format(self.dim1)
-            dim2 = ' ({})'.format(self.dim2)
+            dim1 = f" ({self.dim1})"
+            dim2 = f" ({self.dim2})"
         else:
-            dim1 = ''
-            dim2 = ''
+            dim1 = ""
+            dim2 = ""
 
-        msg = "Cannot convert from '{}'{} to '{}'{}" + self.extra_msg
+        return (
+            f"Cannot convert from '{self.units1}'{dim1} to "
+            f"'{self.units2}'{dim2}{self.extra_msg}"
+        )
 
-        return msg.format(self.units1, dim1, self.units2, dim2)
+    def __reduce__(self):
+        return TypeError.__new__, (DimensionalityError,), self.__dict__
 
 
-class OffsetUnitCalculusError(ValueError):
+class OffsetUnitCalculusError(PintTypeError):
     """Raised on ambiguous operations with offset units.
     """
-    def __init__(self, units1, units2='', extra_msg=''):
-        super().__init__()
-        self.units1 = units1
-        self.units2 = units2
-        self.extra_msg = extra_msg
 
     def __str__(self):
-        msg = ("Ambiguous operation with offset unit (%s)." %
-               ', '.join(['%s' % u for u in [self.units1, self.units2] if u])
-               + " See https://pint.readthedocs.io/en/latest/nonmult.html for guidance."
-               + self.extra_msg)
-        return msg.format(self.units1, self.units2)
+        return (
+            "Ambiguous operation with offset unit (%s)."
+            % ", ".join(str(u) for u in self.args)
+            + " See https://pint.readthedocs.io/en/latest/nonmult.html for guidance."
+        )
 
 
 class UnitStrippedWarning(UserWarning):
diff --git a/pint/quantity.py b/pint/quantity.py
index d7108ba..f0bdd5f 100644
--- a/pint/quantity.py
+++ b/pint/quantity.py
@@ -21,7 +21,7 @@ import warnings
 
 from .formatting import (remove_custom_flags, siunitx_format_unit, ndarray_to_latex,
                          ndarray_to_latex_parts)
-from .errors import (DimensionalityError, OffsetUnitCalculusError,
+from .errors import (DimensionalityError, OffsetUnitCalculusError, PintTypeError,
                      UndefinedUnitError, UnitStrippedWarning)
 from .definitions import UnitDefinition
 from .compat import ndarray, np, _to_magnitude
@@ -625,8 +625,11 @@ class Quantity(PrettyIPython, SharedRegistryObject):
             # other not from same Registry or not a Quantity
             try:
                 other_magnitude = _to_magnitude(other, self.force_ndarray)
+            except PintTypeError:
+                raise
             except TypeError:
                 return NotImplemented
+
             if _eq(other, 0, True):
                 # If the other value is 0 (but not Quantity 0)
                 # do the operation without checking units.
@@ -876,6 +879,8 @@ class Quantity(PrettyIPython, SharedRegistryObject):
                                                   getattr(other, 'units', ''))
             try:
                 other_magnitude = _to_magnitude(other, self.force_ndarray)
+            except PintTypeError:
+                raise
             except TypeError:
                 return NotImplemented
             self._magnitude = magnitude_op(self._magnitude, other_magnitude)
@@ -934,6 +939,8 @@ class Quantity(PrettyIPython, SharedRegistryObject):
                                                   getattr(other, 'units', ''))
             try:
                 other_magnitude = _to_magnitude(other, self.force_ndarray)
+            except PintTypeError:
+                raise
             except TypeError:
                 return NotImplemented
 
@@ -987,6 +994,8 @@ class Quantity(PrettyIPython, SharedRegistryObject):
     def __rtruediv__(self, other):
         try:
             other_magnitude = _to_magnitude(other, self.force_ndarray)
+        except PintTypeError:
+            raise
         except TypeError:
             return NotImplemented
 
@@ -1080,7 +1089,9 @@ class Quantity(PrettyIPython, SharedRegistryObject):
             return self.__pow__(other)
 
         try:
-            other_magnitude = _to_magnitude(other, self.force_ndarray)
+            _to_magnitude(other, self.force_ndarray)
+        except PintTypeError:
+            raise
         except TypeError:
             return NotImplemented
         else:
@@ -1101,9 +1112,12 @@ class Quantity(PrettyIPython, SharedRegistryObject):
                         self._magnitude **= other
                         return self
                 elif np.size(other) > 1:
-                    raise DimensionalityError(self._units, 'dimensionless',
-                                              extra_msg='Quantity array exponents are only allowed '
-                                                        'if the base is dimensionless')
+                    raise DimensionalityError(
+                        self._units,
+                        'dimensionless',
+                        extra_msg=". Quantity array exponents are only allowed if the "
+                        "base is dimensionless"
+                    )
 
             if other == 1:
                 return self
@@ -1130,7 +1144,9 @@ class Quantity(PrettyIPython, SharedRegistryObject):
     @check_implemented
     def __pow__(self, other):
         try:
-            other_magnitude = _to_magnitude(other, self.force_ndarray)
+            _to_magnitude(other, self.force_ndarray)
+        except PintTypeError:
+            raise
         except TypeError:
             return NotImplemented
         else:
@@ -1149,9 +1165,12 @@ class Quantity(PrettyIPython, SharedRegistryObject):
                     else:
                         return self.__class__(self.m ** other)
                 elif np.size(other) > 1:
-                    raise DimensionalityError(self._units, 'dimensionless',
-                                              extra_msg='Quantity array exponents are only allowed '
-                                                        'if the base is dimensionless')
+                    raise DimensionalityError(
+                        self._units,
+                        'dimensionless',
+                        extra_msg=". Quantity array exponents are only allowed if the "
+                        "base is dimensionless"
+                    )
 
             new_self = self
             if other == 1:
@@ -1178,7 +1197,9 @@ class Quantity(PrettyIPython, SharedRegistryObject):
     @check_implemented
     def __rpow__(self, other):
         try:
-            other_magnitude = _to_magnitude(other, self.force_ndarray)
+            _to_magnitude(other, self.force_ndarray)
+        except PintTypeError:
+            raise
         except TypeError:
             return NotImplemented
         else:
@@ -1471,8 +1492,9 @@ class Quantity(PrettyIPython, SharedRegistryObject):
 
     def __getitem__(self, key):
         try:
-            value = self._magnitude[key]
-            return self.__class__(value, self._units)
+            return type(self)(self._magnitude[key], self._units)
+        except PintTypeError:
+            raise
         except TypeError:
             raise TypeError("Neither Quantity object nor its magnitude ({})"
                             "supports indexing".format(self._magnitude))
@@ -1482,7 +1504,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
             if math.isnan(value):
                 self._magnitude[key] = value
                 return
-        except (TypeError, DimensionalityError):
+        except TypeError:
             pass
 
         try:
@@ -1493,17 +1515,24 @@ class Quantity(PrettyIPython, SharedRegistryObject):
 
             if isinstance(factor, self.__class__):
                 if not factor.dimensionless:
-                    raise DimensionalityError(value, self.units,
-                                              extra_msg='. Assign a quantity with the same dimensionality or '
-                                                        'access the magnitude directly as '
-                                                        '`obj.magnitude[%s] = %s`' % (key, value))
+                    raise DimensionalityError(
+                        value,
+                        self.units,
+                        extra_msg=". Assign a quantity with the same dimensionality "
+                        "or access the magnitude directly as "
+                        f"`obj.magnitude[{key}] = {value}`."
+                    )
                 self._magnitude[key] = factor.magnitude
             else:
                 self._magnitude[key] = factor
 
-        except TypeError:
-            raise TypeError("Neither Quantity object nor its magnitude ({})"
-                            "supports indexing".format(self._magnitude))
+        except PintTypeError:
+            raise
+        except TypeError as exc:
+            raise TypeError(
+                f"Neither Quantity object nor its magnitude ({self._magnitude}) "
+                "supports indexing"
+            ) from exc
 
     def tolist(self):
         units = self._units
diff --git a/pint/registry.py b/pint/registry.py
index 13597c0..7e49ab5 100644
--- a/pint/registry.py
+++ b/pint/registry.py
@@ -277,7 +277,9 @@ class BaseRegistry(metaclass=RegistryMeta):
                 for dimension in definition.reference.keys():
                     if dimension in self._dimensions:
                         if dimension != '[]':
-                            raise DefinitionSyntaxError('only one unit per dimension can be a base unit.')
+                            raise DefinitionSyntaxError(
+                                'Only one unit per dimension can be a base unit'
+                            )
                         continue
 
                     self.define(DimensionDefinition(dimension, '', (), None, is_base=True))
@@ -1026,12 +1028,16 @@ class NonMultiplicativeRegistry(BaseRegistry):
         try:
             src_offset_unit = self._validate_and_extract(src)
         except ValueError as ex:
-            raise DimensionalityError(src, dst, extra_msg=' - In source units, %s ' % ex)
+            raise DimensionalityError(
+                src, dst, extra_msg=f" - In source units, {ex}"
+            )
 
         try:
             dst_offset_unit = self._validate_and_extract(dst)
         except ValueError as ex:
-            raise DimensionalityError(src, dst, extra_msg=' - In destination units, %s ' % ex)
+            raise DimensionalityError(
+                src, dst, extra_msg=f" - In destination units, {ex}"
+            )
 
         if not (src_offset_unit or dst_offset_unit):
             return super()._convert(value, src, dst, inplace)
@@ -1096,7 +1102,7 @@ class ContextRegistry(BaseRegistry):
             self.add_context(Context.from_lines(ifile.block_iter(),
                                                 self.get_dimensionality))
         except KeyError as e:
-            raise DefinitionSyntaxError('unknown dimension {} in context'.format(str(e)))
+            raise DefinitionSyntaxError(f'unknown dimension {e} in context')
 
     def add_context(self, context):
         """Add a context object to the registry.
diff --git a/pint/systems.py b/pint/systems.py
index f0ecac5..b9c0029 100644
--- a/pint/systems.py
+++ b/pint/systems.py
@@ -205,8 +205,11 @@ class Group(SharedRegistryObject):
                 # Is a definition
                 definition = Definition.from_string(line)
                 if not isinstance(definition, UnitDefinition):
-                    raise DefinitionSyntaxError('Only UnitDefinition are valid inside _used_groups, '
-                                                'not %s' % type(definition), lineno=lineno)
+                    raise DefinitionSyntaxError(
+                        'Only UnitDefinition are valid inside _used_groups, not '
+                        + str(definition),
+                        lineno=lineno
+                    )
 
                 try:
                     define_func(definition)


Test patch:
diff --git a/pint/testsuite/test_contexts.py b/pint/testsuite/test_contexts.py
index cb33daf..336f4de 100644
--- a/pint/testsuite/test_contexts.py
+++ b/pint/testsuite/test_contexts.py
@@ -3,7 +3,7 @@
 import itertools
 from collections import defaultdict
 
-from pint import UnitRegistry, errors
+from pint import UnitRegistry, DefinitionSyntaxError, DimensionalityError
 from pint.context import Context
 from pint.util import UnitsContainer
 from pint.testsuite import QuantityTestCase
@@ -270,11 +270,11 @@ class TestContexts(QuantityTestCase):
         meter_units = ureg.get_compatible_units(ureg.meter)
         hertz_units = ureg.get_compatible_units(ureg.hertz)
 
-        self.assertRaises(ValueError, q.to, 'Hz')
+        self.assertRaises(DimensionalityError, q.to, 'Hz')
         with ureg.context('lc'):
             self.assertEqual(q.to('Hz'), s)
             self.assertEqual(ureg.get_compatible_units(q), meter_units | hertz_units)
-        self.assertRaises(ValueError, q.to, 'Hz')
+        self.assertRaises(DimensionalityError, q.to, 'Hz')
         self.assertEqual(ureg.get_compatible_units(q), meter_units)
 
     def test_multiple_context(self):
@@ -289,11 +289,11 @@ class TestContexts(QuantityTestCase):
         hertz_units = ureg.get_compatible_units(ureg.hertz)
         ampere_units = ureg.get_compatible_units(ureg.ampere)
 
-        self.assertRaises(ValueError, q.to, 'Hz')
+        self.assertRaises(DimensionalityError, q.to, 'Hz')
         with ureg.context('lc', 'ab'):
             self.assertEqual(q.to('Hz'), s)
             self.assertEqual(ureg.get_compatible_units(q), meter_units | hertz_units | ampere_units)
-        self.assertRaises(ValueError, q.to, 'Hz')
+        self.assertRaises(DimensionalityError, q.to, 'Hz')
         self.assertEqual(ureg.get_compatible_units(q), meter_units)
 
     def test_nested_context(self):
@@ -304,7 +304,7 @@ class TestContexts(QuantityTestCase):
         q = 500 * ureg.meter
         s = (ureg.speed_of_light / q).to('Hz')
 
-        self.assertRaises(ValueError, q.to, 'Hz')
+        self.assertRaises(DimensionalityError, q.to, 'Hz')
         with ureg.context('lc'):
             self.assertEqual(q.to('Hz'), s)
             with ureg.context('ab'):
@@ -312,10 +312,10 @@ class TestContexts(QuantityTestCase):
             self.assertEqual(q.to('Hz'), s)
 
         with ureg.context('ab'):
-            self.assertRaises(ValueError, q.to, 'Hz')
+            self.assertRaises(DimensionalityError, q.to, 'Hz')
             with ureg.context('lc'):
                 self.assertEqual(q.to('Hz'), s)
-            self.assertRaises(ValueError, q.to, 'Hz')
+            self.assertRaises(DimensionalityError, q.to, 'Hz')
 
     def test_context_with_arg(self):
 
@@ -326,7 +326,7 @@ class TestContexts(QuantityTestCase):
         q = 500 * ureg.meter
         s = (ureg.speed_of_light / q).to('Hz')
 
-        self.assertRaises(ValueError, q.to, 'Hz')
+        self.assertRaises(DimensionalityError, q.to, 'Hz')
         with ureg.context('lc', n=1):
             self.assertEqual(q.to('Hz'), s)
             with ureg.context('ab'):
@@ -334,10 +334,10 @@ class TestContexts(QuantityTestCase):
             self.assertEqual(q.to('Hz'), s)
 
         with ureg.context('ab'):
-            self.assertRaises(ValueError, q.to, 'Hz')
+            self.assertRaises(DimensionalityError, q.to, 'Hz')
             with ureg.context('lc', n=1):
                 self.assertEqual(q.to('Hz'), s)
-            self.assertRaises(ValueError, q.to, 'Hz')
+            self.assertRaises(DimensionalityError, q.to, 'Hz')
 
         with ureg.context('lc'):
             self.assertRaises(TypeError, q.to, 'Hz')
@@ -351,7 +351,7 @@ class TestContexts(QuantityTestCase):
         q = 500 * ureg.meter
         s = (ureg.speed_of_light / q).to('Hz')
 
-        self.assertRaises(ValueError, q.to, 'Hz')
+        self.assertRaises(DimensionalityError, q.to, 'Hz')
         ureg.enable_contexts('lc', n=1)
         self.assertEqual(q.to('Hz'), s)
         ureg.enable_contexts('ab')
@@ -361,11 +361,11 @@ class TestContexts(QuantityTestCase):
         ureg.disable_contexts(1)
 
         ureg.enable_contexts('ab')
-        self.assertRaises(ValueError, q.to, 'Hz')
+        self.assertRaises(DimensionalityError, q.to, 'Hz')
         ureg.enable_contexts('lc', n=1)
         self.assertEqual(q.to('Hz'), s)
         ureg.disable_contexts(1)
-        self.assertRaises(ValueError, q.to, 'Hz')
+        self.assertRaises(DimensionalityError, q.to, 'Hz')
         ureg.disable_contexts(1)
 
         ureg.enable_contexts('lc')
@@ -381,7 +381,7 @@ class TestContexts(QuantityTestCase):
         q = 500 * ureg.meter
         s = (ureg.speed_of_light / q).to('Hz')
 
-        self.assertRaises(ValueError, q.to, 'Hz')
+        self.assertRaises(DimensionalityError, q.to, 'Hz')
         with ureg.context('lc'):
             self.assertEqual(q.to('Hz'), s)
             with ureg.context('ab'):
@@ -389,12 +389,12 @@ class TestContexts(QuantityTestCase):
             self.assertEqual(q.to('Hz'), s)
 
         with ureg.context('ab'):
-            self.assertRaises(ValueError, q.to, 'Hz')
+            self.assertRaises(DimensionalityError, q.to, 'Hz')
             with ureg.context('lc'):
                 self.assertEqual(q.to('Hz'), s)
-            self.assertRaises(ValueError, q.to, 'Hz')
+            self.assertRaises(DimensionalityError, q.to, 'Hz')
 
-        self.assertRaises(ValueError, q.to, 'Hz')
+        self.assertRaises(DimensionalityError, q.to, 'Hz')
         with ureg.context('lc', n=2):
             self.assertEqual(q.to('Hz'), s / 2)
             with ureg.context('ab'):
@@ -402,10 +402,10 @@ class TestContexts(QuantityTestCase):
             self.assertEqual(q.to('Hz'), s / 2)
 
         with ureg.context('ab'):
-            self.assertRaises(ValueError, q.to, 'Hz')
+            self.assertRaises(DimensionalityError, q.to, 'Hz')
             with ureg.context('lc', n=2):
                 self.assertEqual(q.to('Hz'), s / 2)
-            self.assertRaises(ValueError, q.to, 'Hz')
+            self.assertRaises(DimensionalityError, q.to, 'Hz')
 
     def test_context_with_sharedarg_def(self):
 
@@ -466,7 +466,7 @@ class TestContexts(QuantityTestCase):
         ureg.enable_contexts(c)
         self.assertQuantityEqual(x.to("s"), expect)
         ureg.disable_contexts(1)
-        self.assertRaises(errors.DimensionalityError, x.to, "s")
+        self.assertRaises(DimensionalityError, x.to, "s")
 
         # Multiple anonymous contexts
         c2 = Context()
@@ -511,7 +511,7 @@ class TestContexts(QuantityTestCase):
              '[length] = 1 / [time]: c / value',
              '1 / [time] = [length]: c / value']
 
-        self.assertRaises(ValueError, Context.from_lines, s)
+        self.assertRaises(DefinitionSyntaxError, Context.from_lines, s)
 
     def test_parse_simple(self):
 
@@ -595,7 +595,7 @@ class TestContexts(QuantityTestCase):
         # If the variable is not present in the definition, then raise an error
         s = ['@context(n=1) longcontextname',
              '[length] <-> 1 / [time]: c / value']
-        self.assertRaises(ValueError, Context.from_lines, s)
+        self.assertRaises(DefinitionSyntaxError, Context.from_lines, s)
 
     def test_warnings(self):
 
@@ -658,7 +658,7 @@ class TestDefinedContexts(QuantityTestCase):
     def test_textile(self):
         ureg = self.ureg
         qty_direct = 1.331 * ureg.tex
-        with self.assertRaises(errors.DimensionalityError):
+        with self.assertRaises(DimensionalityError):
             qty_indirect = qty_direct.to('Nm')
 
         with ureg.context('textile'):
@@ -683,7 +683,7 @@ class TestDefinedContexts(QuantityTestCase):
         def f(wl):
             return wl.to('terahertz')
 
-        self.assertRaises(errors.DimensionalityError, f, a)
+        self.assertRaises(DimensionalityError, f, a)
 
         @ureg.with_context('sp')
         def g(wl):
diff --git a/pint/testsuite/test_errors.py b/pint/testsuite/test_errors.py
index 0158861..56ae752 100644
--- a/pint/testsuite/test_errors.py
+++ b/pint/testsuite/test_errors.py
@@ -1,18 +1,119 @@
 # -*- coding: utf-8 -*-
 
-from pint import DimensionalityError, UndefinedUnitError
+import pickle
+
+from pint import (
+    DefinitionSyntaxError,
+    DimensionalityError,
+    Quantity,
+    OffsetUnitCalculusError,
+    RedefinitionError,
+    UndefinedUnitError,
+    UnitRegistry,
+)
 from pint.testsuite import BaseTestCase
 
 
 class TestErrors(BaseTestCase):
+    def test_definition_syntax_error(self):
+        ex = DefinitionSyntaxError("foo")
+        self.assertEqual(str(ex), "foo")
+
+        # filename and lineno can be attached after init
+        ex.filename = "a.txt"
+        ex.lineno = 123
+        self.assertEqual(str(ex), "While opening a.txt, in line 123: foo")
+
+        ex = DefinitionSyntaxError("foo", lineno=123)
+        self.assertEqual(str(ex), "In line 123: foo")
+
+        ex = DefinitionSyntaxError("foo", filename="a.txt")
+        self.assertEqual(str(ex), "While opening a.txt: foo")
+
+        ex = DefinitionSyntaxError("foo", filename="a.txt", lineno=123)
+        self.assertEqual(str(ex), "While opening a.txt, in line 123: foo")
+
+    def test_redefinition_error(self):
+        ex = RedefinitionError("foo", "bar")
+        self.assertEqual(str(ex), "Cannot redefine 'foo' (bar)")
+
+        # filename and lineno can be attached after init
+        ex.filename = "a.txt"
+        ex.lineno = 123
+        self.assertEqual(
+            str(ex), "While opening a.txt, in line 123: Cannot redefine 'foo' (bar)"
+        )
+
+        ex = RedefinitionError("foo", "bar", lineno=123)
+        self.assertEqual(str(ex), "In line 123: Cannot redefine 'foo' (bar)")
 
-    def test_errors(self):
-        x = ('meter', )
+        ex = RedefinitionError("foo", "bar", filename="a.txt")
+        self.assertEqual(str(ex), "While opening a.txt: Cannot redefine 'foo' (bar)")
+
+        ex = RedefinitionError("foo", "bar", filename="a.txt", lineno=123)
+        self.assertEqual(
+            str(ex), "While opening a.txt, in line 123: Cannot redefine 'foo' (bar)"
+        )
+
+    def test_undefined_unit_error(self):
+        x = ("meter",)
         msg = "'meter' is not defined in the unit registry"
         self.assertEqual(str(UndefinedUnitError(x)), msg)
         self.assertEqual(str(UndefinedUnitError(list(x))), msg)
         self.assertEqual(str(UndefinedUnitError(set(x))), msg)
 
-        msg = "Cannot convert from 'a' (c) to 'b' (d)msg"
-        ex = DimensionalityError('a', 'b', 'c', 'd', 'msg')
-        self.assertEqual(str(ex), msg)
+    def test_undefined_unit_error_multi(self):
+        x = ("meter", "kg")
+        msg = "('meter', 'kg') are not defined in the unit registry"
+        self.assertEqual(str(UndefinedUnitError(x)), msg)
+        self.assertEqual(str(UndefinedUnitError(list(x))), msg)
+
+    def test_dimensionality_error(self):
+        ex = DimensionalityError("a", "b")
+        self.assertEqual(str(ex), "Cannot convert from 'a' to 'b'")
+        ex = DimensionalityError("a", "b", "c")
+        self.assertEqual(str(ex), "Cannot convert from 'a' (c) to 'b' ()")
+        ex = DimensionalityError("a", "b", "c", "d", extra_msg=": msg")
+        self.assertEqual(str(ex), "Cannot convert from 'a' (c) to 'b' (d): msg")
+
+    def test_offset_unit_calculus_error(self):
+        ex = OffsetUnitCalculusError(Quantity("1 kg")._units)
+        self.assertEqual(
+            str(ex),
+            "Ambiguous operation with offset unit (kilogram). See "
+            "https://pint.readthedocs.io/en/latest/nonmult.html for guidance.",
+        )
+        ex = OffsetUnitCalculusError(Quantity("1 kg")._units, Quantity("1 s")._units)
+        self.assertEqual(
+            str(ex),
+            "Ambiguous operation with offset unit (kilogram, second). See "
+            "https://pint.readthedocs.io/en/latest/nonmult.html for guidance.",
+        )
+
+    def test_pickle_definition_syntax_error(self):
+        # OffsetUnitCalculusError raised from a custom ureg must be pickleable even if
+        # the ureg is not registered as the application ureg
+        ureg = UnitRegistry(filename=None)
+        ureg.define("foo = [bar]")
+        ureg.define("bar = 2 foo")
+        pik = pickle.dumps(ureg.Quantity("1 foo"))
+        with self.assertRaises(UndefinedUnitError):
+            pickle.loads(pik)
+        q1 = ureg.Quantity("1 foo")
+        q2 = ureg.Quantity("1 bar")
+
+        for ex in [
+            DefinitionSyntaxError("foo", filename="a.txt", lineno=123),
+            RedefinitionError("foo", "bar"),
+            UndefinedUnitError("meter"),
+            DimensionalityError("a", "b", "c", "d", extra_msg=": msg"),
+            OffsetUnitCalculusError(Quantity("1 kg")._units, Quantity("1 s")._units),
+            OffsetUnitCalculusError(q1._units, q2._units),
+        ]:
+            with self.subTest(etype=type(ex)):
+                # assert False, ex.__reduce__()
+                ex2 = pickle.loads(pickle.dumps(ex))
+                assert type(ex) is type(ex2)
+                self.assertEqual(ex.args, ex2.args)
+                self.assertEqual(ex.__dict__, ex2.__dict__)
+                self.assertEqual(str(ex), str(ex2))
diff --git a/pint/testsuite/test_issues.py b/pint/testsuite/test_issues.py
index 0254550..9f5fcd7 100644
--- a/pint/testsuite/test_issues.py
+++ b/pint/testsuite/test_issues.py
@@ -3,6 +3,7 @@
 import copy
 import math
 import unittest
+import pprint
 
 from pint import DimensionalityError, UnitRegistry
 from pint.unit import UnitsContainer
@@ -658,3 +659,13 @@ class TestIssues(QuantityTestCase):
         assert (hash(-1) == hash(-3)) == (hash(a) == hash(c))
         assert a != b
         assert a != c
+
+    def test_issue912(self):
+        """pprint.pformat() invokes sorted() on large sets and frozensets and graciously
+        handles TypeError, but not generic Exceptions. This test will fail if
+        pint.DimensionalityError stops being a subclass of TypeError.
+        """
+        ureg = UnitRegistry()
+        meter_units = ureg.get_compatible_units(ureg.meter)
+        hertz_units = ureg.get_compatible_units(ureg.hertz)
+        pprint.pformat(meter_units | hertz_units)
diff --git a/pint/testsuite/test_measurement.py b/pint/testsuite/test_measurement.py
index ac4260f..47edc5c 100644
--- a/pint/testsuite/test_measurement.py
+++ b/pint/testsuite/test_measurement.py
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 
+from pint import DimensionalityError
 from pint.testsuite import QuantityTestCase, helpers
 
 
@@ -102,9 +103,12 @@ class TestMeasurement(QuantityTestCase):
         o = self.Q_(.1, 'm')
 
         M_ = self.ureg.Measurement
-        self.assertRaises(ValueError, M_, v, o)
-        self.assertRaises(ValueError, v.plus_minus, o)
-        self.assertRaises(ValueError, v.plus_minus, u, True)
+        with self.assertRaises(DimensionalityError):
+            M_(v, o)
+        with self.assertRaises(DimensionalityError):
+            v.plus_minus(o)
+        with self.assertRaises(ValueError):
+            v.plus_minus(u, relative=True)
 
     def test_propagate_linear(self):
 
diff --git a/pint/testsuite/test_numpy.py b/pint/testsuite/test_numpy.py
index f821315..1697ba8 100644
--- a/pint/testsuite/test_numpy.py
+++ b/pint/testsuite/test_numpy.py
@@ -79,8 +79,10 @@ class TestNumpyMethods(QuantityTestCase):
         self.assertQuantityEqual(q, [0.001, 2., 0.002, 4.]*self.ureg.m/self.ureg.mm)
 
         q =  [1., 2., 3., 4.] * self.ureg.m
-        self.assertRaises(ValueError, q.put, [0, 2], [4., 6.] * self.ureg.J)
-        self.assertRaises(ValueError, q.put, [0, 2], [4., 6.])
+        with self.assertRaises(DimensionalityError):
+            q.put([0, 2], [4., 6.] * self.ureg.J)
+        with self.assertRaises(DimensionalityError):
+            q.put([0, 2], [4., 6.])
 
     def test_repeat(self):
         self.assertQuantityEqual(self.q.repeat(2), [1,1,2,2,3,3,4,4]*self.ureg.m)
@@ -109,7 +111,7 @@ class TestNumpyMethods(QuantityTestCase):
         np.testing.assert_array_equal(q.searchsorted([1.5, 2.5] * self.ureg.m),
                                       [1, 2])
         q = self.q.flatten()
-        self.assertRaises(ValueError, q.searchsorted, [1.5, 2.5])
+        self.assertRaises(DimensionalityError, q.searchsorted, [1.5, 2.5])
 
     def test_searchsorted_numpy_func(self):
         """Test searchsorted as numpy function."""
@@ -149,8 +151,8 @@ class TestNumpyMethods(QuantityTestCase):
             self.q.clip(min=2*self.ureg.m, max=3*self.ureg.m),
             [[2, 2], [3, 3]] * self.ureg.m
         )
-        self.assertRaises(ValueError, self.q.clip, self.ureg.J)
-        self.assertRaises(ValueError, self.q.clip, 1)
+        self.assertRaises(DimensionalityError, self.q.clip, self.ureg.J)
+        self.assertRaises(DimensionalityError, self.q.clip, 1)
 
     def test_round(self):
         q = [1, 1.33, 5.67, 22] * self.ureg.m
@@ -177,7 +179,7 @@ class TestNumpyMethods(QuantityTestCase):
         self.assertEqual(self.q.prod(), 24 * self.ureg.m**4)
 
     def test_cumprod(self):
-        self.assertRaises(ValueError, self.q.cumprod)
+        self.assertRaises(DimensionalityError, self.q.cumprod)
         self.assertQuantityEqual((self.q / self.ureg.m).cumprod(), [1, 2, 6, 24])
 
     @helpers.requires_numpy_previous_than('1.10')
@@ -200,34 +202,39 @@ class TestNumpyMethods(QuantityTestCase):
         self.assertEqual(self.q[1,1], 4*self.ureg.m)
 
     def test_setitem(self):
-        self.assertRaises(ValueError, self.q.__setitem__, (0,0), 1)
-        self.assertRaises(ValueError, self.q.__setitem__, (0,0), 1*self.ureg.J)
-        self.assertRaises(ValueError, self.q.__setitem__, 0, 1)
-        self.assertRaises(ValueError, self.q.__setitem__, 0, np.ndarray([1, 2]))
-        self.assertRaises(ValueError, self.q.__setitem__, 0, 1*self.ureg.J)
+        with self.assertRaises(TypeError):
+            self.q[0, 0] = 1
+        with self.assertRaises(DimensionalityError):
+            self.q[0, 0] = 1 * self.ureg.J
+        with self.assertRaises(DimensionalityError):
+            self.q[0] = 1
+        with self.assertRaises(DimensionalityError):
+            self.q[0] = np.ndarray([1, 2])
+        with self.assertRaises(DimensionalityError):
+            self.q[0] = 1 * self.ureg.J
 
         q = self.q.copy()
-        q[0] = 1*self.ureg.m
-        self.assertQuantityEqual(q, [[1,1],[3,4]]*self.ureg.m)
+        q[0] = 1 * self.ureg.m
+        self.assertQuantityEqual(q, [[1, 1], [3, 4]] * self.ureg.m)
 
         q = self.q.copy()
-        q.__setitem__(Ellipsis, 1*self.ureg.m)
-        self.assertQuantityEqual(q, [[1,1],[1,1]]*self.ureg.m)
+        q[...] = 1 * self.ureg.m
+        self.assertQuantityEqual(q, [[1, 1], [1, 1]] * self.ureg.m)
 
         q = self.q.copy()
-        q[:] = 1*self.ureg.m
-        self.assertQuantityEqual(q, [[1,1],[1,1]]*self.ureg.m)
+        q[:] = 1 * self.ureg.m
+        self.assertQuantityEqual(q, [[1, 1], [1, 1]] * self.ureg.m)
 
         # check and see that dimensionless num  bers work correctly
-        q = [0,1,2,3]*self.ureg.dimensionless
+        q = [0, 1, 2, 3] * self.ureg.dimensionless
         q[0] = 1
-        self.assertQuantityEqual(q, np.asarray([1,1,2,3]))
+        self.assertQuantityEqual(q, np.asarray([1, 1, 2, 3]))
         q[0] = self.ureg.m/self.ureg.mm
-        self.assertQuantityEqual(q, np.asarray([1000, 1,2,3]))
+        self.assertQuantityEqual(q, np.asarray([1000, 1, 2, 3]))
 
-        q = [0.,1.,2.,3.] * self.ureg.m / self.ureg.mm
-        q[0] = 1.
-        self.assertQuantityEqual(q, [0.001,1,2,3]*self.ureg.m / self.ureg.mm)
+        q = [0., 1., 2., 3.] * self.ureg.m / self.ureg.mm
+        q[0] = 1.0
+        self.assertQuantityEqual(q, [0.001, 1, 2, 3] * self.ureg.m / self.ureg.mm)
 
     def test_iterator(self):
         for q, v in zip(self.q.flatten(), [1, 2, 3, 4]):
diff --git a/pint/testsuite/test_umath.py b/pint/testsuite/test_umath.py
index d4fa9c6..f9bfdcc 100644
--- a/pint/testsuite/test_umath.py
+++ b/pint/testsuite/test_umath.py
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 
+from pint import DimensionalityError
 from pint.compat import np
 from pint.testsuite import QuantityTestCase, helpers
 
@@ -38,15 +39,6 @@ class TestUFuncs(QuantityTestCase):
     def qi(self):
         return np.asarray([1 + 1j, 2 + 2j, 3 + 3j, 4 + 4j]) * self.ureg.m
 
-    def assertRaisesMsg(self, msg, ExcType, func, *args, **kwargs):
-        try:
-            func(*args, **kwargs)
-            self.assertFalse(True, msg='Exception {} not raised {}'.format(ExcType, msg))
-        except ExcType as e:
-            pass
-        except Exception as e:
-            self.assertFalse(True, msg='{} not raised but {}\n{}'.format(ExcType, e, msg))
-
     def _test1(self, func,  ok_with, raise_with=(), output_units='same', results=None, rtol=1e-6):
         """Test function that takes a single argument and returns Quantity.
 
@@ -83,8 +75,10 @@ class TestUFuncs(QuantityTestCase):
             self.assertQuantityAlmostEqual(qm, res, rtol=rtol, msg=err_msg)
 
         for x1 in raise_with:
-            self.assertRaisesMsg('At {} with {}'.format(func.__name__, x1),
-                                 ValueError, func, x1)
+            with self.assertRaises(
+                DimensionalityError, msg=f'At {func.__name__} with {x1}'
+            ):
+                func(x1)
 
     def _testn(self, func,  ok_with, raise_with=(), results=None):
         """Test function that takes a single argument and returns and ndarray (not a Quantity)
@@ -136,8 +130,8 @@ class TestUFuncs(QuantityTestCase):
                 self.assertQuantityAlmostEqual(qm, re, rtol=rtol, msg=err_msg)
 
         for x1 in raise_with:
-            self.assertRaisesMsg('At {} with {}'.format(func.__name__, x1),
-                                 ValueError, func, x1)
+            with self.assertRaises(ValueError, msg=f'At {func.__name__} with {x1}'):
+                func(x1)
 
     def _test2(self, func, x1, ok_with, raise_with=(), output_units='same', rtol=1e-6, convert2=True):
         """Test function that takes two arguments and return a Quantity.
@@ -183,8 +177,10 @@ class TestUFuncs(QuantityTestCase):
             self.assertQuantityAlmostEqual(qm, res, rtol=rtol, msg=err_msg)
 
         for x2 in raise_with:
-            self.assertRaisesMsg('At {} with {} and {}'.format(func.__name__, x1, x2),
-                                 ValueError, func, x1, x2)
+            with self.assertRaises(
+                DimensionalityError, msg=f'At {func.__name__} with {x1} and {x2}'
+            ):
+                func(x1, x2)
 
     def _testn2(self, func, x1, ok_with, raise_with=()):
         """Test function that takes two arguments and return a ndarray.
@@ -471,7 +467,8 @@ class TestTrigUfuncs(TestUFuncs):
     def test_hypot(self):
         self.assertTrue(np.hypot(3. * self.ureg.m, 4. * self.ureg.m) ==  5. * self.ureg.m)
         self.assertTrue(np.hypot(3. * self.ureg.m, 400. * self.ureg.cm) ==  5. * self.ureg.m)
-        self.assertRaises(ValueError, np.hypot, 1. * self.ureg.m, 2. * self.ureg.J)
+        with self.assertRaises(DimensionalityError):
+            np.hypot(1. * self.ureg.m, 2. * self.ureg.J)
 
     def test_sinh(self):
         self._test1(np.sinh, (np.arange(0, pi/2, pi/4) * self.ureg.dimensionless,
diff --git a/pint/testsuite/test_unit.py b/pint/testsuite/test_unit.py
index 82dada1..1932117 100644
--- a/pint/testsuite/test_unit.py
+++ b/pint/testsuite/test_unit.py
@@ -5,9 +5,11 @@ import functools
 import math
 import re
 
-from pint import DimensionalityError, UndefinedUnitError
 from pint.compat import np
 from pint.registry import (UnitRegistry, LazyRegistry)
+from pint import (
+    DefinitionSyntaxError, DimensionalityError, RedefinitionError, UndefinedUnitError
+)
 from pint.testsuite import QuantityTestCase, helpers
 from pint.testsuite.parameterized import ParameterizedTestCase
 from pint.util import (UnitsContainer, ParserHelper)
@@ -174,7 +176,7 @@ class TestRegistry(QuantityTestCase):
     def test_base(self):
         ureg = UnitRegistry(None)
         ureg.define('meter = [length]')
-        self.assertRaises(ValueError, ureg.define, 'meter = [length]')
+        self.assertRaises(DefinitionSyntaxError, ureg.define, 'meter = [length]')
         self.assertRaises(TypeError, ureg.define, list())
         x = ureg.define('degC = kelvin; offset: 273.15')
 
@@ -228,14 +230,6 @@ class TestRegistry(QuantityTestCase):
         self.assertEqual(self.ureg.parse_expression('kilometre'), self.Q_(1, UnitsContainer(kilometer=1.)))
         self.assertEqual(self.ureg.parse_expression('kilometres'), self.Q_(1, UnitsContainer(kilometer=1.)))
 
-    def test_str_errors(self):
-        self.assertEqual(str(UndefinedUnitError('rabbits')), "'{0!s}' is not defined in the unit registry".format('rabbits'))
-        self.assertEqual(str(UndefinedUnitError(('rabbits', 'horses'))), "'{0!s}' are not defined in the unit registry".format(('rabbits', 'horses')))
-        self.assertEqual(str(DimensionalityError('meter', 'second')),
-                         "Cannot convert from 'meter' to 'second'")
-        self.assertEqual(str(DimensionalityError('meter', 'second', 'length', 'time')),
-                         "Cannot convert from 'meter' (length) to 'second' (time)")
-
     def test_parse_mul_div(self):
         self.assertEqual(self.ureg.parse_expression('meter*meter'), self.Q_(1, UnitsContainer(meter=2.)))
         self.assertEqual(self.ureg.parse_expression('meter**2'), self.Q_(1, UnitsContainer(meter=2.)))
@@ -341,19 +335,19 @@ class TestRegistry(QuantityTestCase):
         self.assertRaises(ValueError, f1, 3.)
         self.assertEqual(f1(3. * ureg.centimeter), 0.03)
         self.assertEqual(f1(3. * ureg.meter), 3.)
-        self.assertRaises(ValueError, f1, 3 * ureg.second)
+        self.assertRaises(DimensionalityError, f1, 3 * ureg.second)
 
         f1b = ureg.wraps(None, [ureg.meter, ])(func)
         self.assertRaises(ValueError, f1b, 3.)
         self.assertEqual(f1b(3. * ureg.centimeter), 0.03)
         self.assertEqual(f1b(3. * ureg.meter), 3.)
-        self.assertRaises(ValueError, f1b, 3 * ureg.second)
+        self.assertRaises(DimensionalityError, f1b, 3 * ureg.second)
 
         f1 = ureg.wraps(None, 'meter')(func)
         self.assertRaises(ValueError, f1, 3.)
         self.assertEqual(f1(3. * ureg.centimeter), 0.03)
         self.assertEqual(f1(3. * ureg.meter), 3.)
-        self.assertRaises(ValueError, f1, 3 * ureg.second)
+        self.assertRaises(DimensionalityError, f1, 3 * ureg.second)
 
         f2 = ureg.wraps('centimeter', ['meter', ])(func)
         self.assertRaises(ValueError, f2, 3.)
@@ -613,14 +607,15 @@ class TestRegistryWithDefaultRegistry(TestRegistry):
 
     def test_redefinition(self):
         d = self.ureg.define
-        self.assertRaises(ValueError, d, 'meter = [time]')
-        self.assertRaises(ValueError, d, 'kilo- = 1000')
-        self.assertRaises(ValueError, d, '[speed] = [length]')
+        self.assertRaises(DefinitionSyntaxError, d, 'meter = [time]')
+        self.assertRaises(RedefinitionError, d, 'meter = [newdim]')
+        self.assertRaises(RedefinitionError, d, 'kilo- = 1000')
+        self.assertRaises(RedefinitionError, d, '[speed] = [length]')
 
         # aliases
         self.assertIn('inch', self.ureg._units)
-        self.assertRaises(ValueError, d, 'bla = 3.2 meter = inch')
-        self.assertRaises(ValueError, d, 'myk- = 1000 = kilo-')
+        self.assertRaises(RedefinitionError, d, 'bla = 3.2 meter = inch')
+        self.assertRaises(RedefinitionError, d, 'myk- = 1000 = kilo-')
 
 
 class TestConvertWithOffset(QuantityTestCase, ParameterizedTestCase):