Repository: hgrecco/pint

Issue:
Multiplication of quantity by dictionary-like object
I want to multiply a `quantity` by a `dict`-like object which has its own multiply defined. I would like for `pint` to return a `NotImplemented` when it tries to multiply a `quantity` by an object of my type, forcing the multiply to be done using my object's multiply. Here's what currently happens:

```python
import collections
import pint

ureg = pint.UnitRegistry()
q = 5 * ureg.s
c = collections.Counter({1:2, 3:4})
q * c
AttributeError: 'NotImplementedType' object has no attribute '_REGISTRY'
```
I understand that `collections.Counter` doesn't have its own multiply defined, but the error I get is the same if I attempt to multiply the `quantity` by my object with the `quantity` on the left (if the `quantity` is on the right, my object's multiply is used). The actual error here occurs in `pint.quantity.ireduce_dimensions`, when the result of calling the input function `f` is `NotImplemented`.

Discussion:

Patch:
diff --git a/CHANGES b/CHANGES
index b21be59..27a6c38 100644
--- a/CHANGES
+++ b/CHANGES
@@ -12,6 +12,7 @@ Pint Changelog
   end_values (Issue #1026)
 - Reinstated support for pickle protocol 0 and 1, which is required by pytables
   (Issue #1036, Thanks Guido Imperiale)
+- Fixed bug with multiplication of Quantity by dict (Issue #1032)
 - Bare zeros and NaNs (not wrapped by Quantity) are now gracefully accepted by all numpy
   operations; e.g. np.stack([Quantity([1, 2], "m"), [0, np.nan]) is now valid, whereas
   np.stack([Quantity([1, 2], "m"), [3, 4]) will continue raising DimensionalityError.
diff --git a/pint/quantity.py b/pint/quantity.py
index 454c462..c1ade31 100644
--- a/pint/quantity.py
+++ b/pint/quantity.py
@@ -74,9 +74,12 @@ class _Exception(Exception):  # pragma: no cover
 def reduce_dimensions(f):
     def wrapped(self, *args, **kwargs):
         result = f(self, *args, **kwargs)
-        if result._REGISTRY.auto_reduce_dimensions:
-            return result.to_reduced_units()
-        else:
+        try:
+            if result._REGISTRY.auto_reduce_dimensions:
+                return result.to_reduced_units()
+            else:
+                return result
+        except AttributeError:
             return result
 
     return wrapped
@@ -85,8 +88,11 @@ def reduce_dimensions(f):
 def ireduce_dimensions(f):
     def wrapped(self, *args, **kwargs):
         result = f(self, *args, **kwargs)
-        if result._REGISTRY.auto_reduce_dimensions:
-            result.ito_reduced_units()
+        try:
+            if result._REGISTRY.auto_reduce_dimensions:
+                result.ito_reduced_units()
+        except AttributeError:
+            pass
         return result
 
     return wrapped


Test patch:
diff --git a/pint/testsuite/test_issues.py b/pint/testsuite/test_issues.py
index a3be41f..29c7905 100644
--- a/pint/testsuite/test_issues.py
+++ b/pint/testsuite/test_issues.py
@@ -671,6 +671,19 @@ class TestIssues(QuantityTestCase):
         assert q.units == ureg.nanometer
         assert q.magnitude == 1
 
+    def test_issue1032(self):
+        class MultiplicativeDictionary(dict):
+            def __rmul__(self, other):
+                return self.__class__(
+                    {key: value * other for key, value in self.items()}
+                )
+
+        q = 3 * ureg.s
+        d = MultiplicativeDictionary({4: 5, 6: 7})
+        assert q * d == MultiplicativeDictionary({4: 15 * ureg.s, 6: 21 * ureg.s})
+        with self.assertRaises(TypeError):
+            d * q
+
     @helpers.requires_numpy()
     def test_issue973(self):
         """Verify that an empty array Quantity can be created through multiplication."""