Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

subclassing quantity breaks __setitem__ with no useful error message #826

Open
Tigraan opened this issue Jul 2, 2019 · 1 comment
Open
Labels
numpy Numpy related bug/enhancement

Comments

@Tigraan
Copy link

Tigraan commented Jul 2, 2019

The problem

Code to reproduce: (pint 0.9)

import pint
UREG = pint.UnitRegistry()
Q_ = UREG.Quantity

class QClone(Q_):
    pass

arr = QClone([1.0, 2.0], 'm')
arr[0] = Q_(0.0, 'm')

causes the error

Traceback (most recent call last):
  File "<the test file>", line 9, in <module>
    arr[0] = Q_(0.0, 'm')
  File "...\anaconda2\lib\site-packages\pint\quantity.py", line 1425, in __setitem__
    '`obj.magnitude[%s] = %s`' % (key, value))
pint.errors.DimensionalityError: Cannot convert from '0.0 meter' to 'meter'. Assign a quantity with the same dimensionality or access the magnitude directly as `obj.magnitude[0] = 0.0 meter`

The error message is not very helpful. Inspecting the two objects reveals they share the unit registry, so it is not a case of unit systems.

What happens
The subclassing causes the __setitem__ method to take a certain conditional branch. Relevant excerpt for reference

# self is the array we set an item of, value is what we put in
if isinstance(value, self.__class__):  
    factor = self.__class__(value.magnitude, value._units / self._units).to_root_units()
else:
    factor = self.__class__(value, self._units ** (-1)).to_root_units()

if isinstance(factor, self.__class__):  # not sure that test is ever False BTW
    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))

Because the value we try to put in is of the parent class, it is not an instance of the child class the array has, and isinstance(value, self.__class__) == False. It causes factor to become a dimensional quantity and fail the subsequent test. The resulting error message is nonsensical ("cannot convert some_unit to some_unit" is hardly helpful for debugging).

What could be done

Option 1 : nothing. (Whoever subclasses the pint classes is responsible for making them work, redefining methods etc.)

Option 2 : if the test is actually here to detect whether value is a dimensional quantity, check the relevant attributes directly (EAFP): replace the first part of the snippet by

try:
    mag, un = value.magnitude, value._units / self._units
except AttributeError:
    mag, un = value, self._units ** (-1)
factor = self.__class__(mag, un).to_root_units()

I do not know enough about pint's design to confidently suggest a pull request for option 2 before asking first. There might also be another option I missed.

@hgrecco
Copy link
Owner

hgrecco commented Jul 24, 2019

There is a plan to split Scalar from Array Quantities to simplify working with pandas and other tabular oriented frameworks. I think that might help.

@hgrecco hgrecco added the numpy Numpy related bug/enhancement label Dec 17, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
numpy Numpy related bug/enhancement
Projects
None yet
Development

No branches or pull requests

2 participants