hgrecco / pint

Operate and manipulate physical quantities in Python
http://pint.readthedocs.org/
Other
2.36k stars 466 forks source link

No doc for type checker usage (e.g. with mypy) #1577

Open conversy opened 2 years ago

conversy commented 2 years ago

Hi,

following this issue and this PR on typing/annotation, I could not find a documentation or a tutorial that would explain how to use pint with mypyor other Python checkers, together with the limitations.

Would it be possible to discuss here a few examples of code and usage, before creating a doc entry PR for this topic? That would be very useful!

Best,

Stéphane

PS: I first asked a similar question in the PR discussion, but I figured it would be best to submit a documentation issue.

conversy commented 1 year ago

after some investigations, I ended up with this example of how to best use Pint with mypy:

import pint
ureg = pint.UnitRegistry()

class Human:
    height: pint.Quantity = 0 * ureg.centimeter                        # initialize to a preferred unit/dimensionality
    def __init__(self, height: pint.Quantity = 0 * ureg.centimeter):   # idem for default value
        height.check('[length]')                                       # check dimensionality
        # assert(height.dimensionality == self.height.dimensionality)  # check dimensionality variant
        self.height = height.to(ureg.centimeter)                       # optional call to .to, keep the field as centimeters

h1 = Human(180 * ureg.centimeter)  # mypy ok, assertions ok
h2 = Human(6 * ureg.feet)          # mypy ok, assertions still ok
print(h2.height)
h3 = Human(180)                    # mypy error: not a pint.Quantity
h4 = Human(180 * ureg.second)      # mypy ok, assertions error: cannot convert units

rationale:

I hope this is helpful, I may be completely wrong though...

conversy commented 1 year ago
import pint
ureg = pint.UnitRegistry()

# raise DimensionalityError if not compatible
def assert_compatible_with(a,b):
    b.to(a.units)

class Human:
    def __init__(self, height: pint.Quantity = 0 * ureg.cm):   # default value in a preferred unit/dimensionality
        height.check('[length]')                               # check dimensionality
        assert_compatible_with(height, 0 * ureg.cm)            # variant
        self.height = height.to(ureg.centimeter)               # optional call to .to, keep the field as centimeters

h1 = Human(180 * ureg.centimeter)  # mypy ok, assertions ok
h2 = Human(6 * ureg.feet)          # mypy ok, assertions still ok
h3 = Human(180)                    # mypy error: not a pint.Quantity
h4 = Human(180 * ureg.second)      # mypy ok, assertions error: cannot convert units

In this version, I use an assert that raises the corresponding exceptions with a useful message. Is there such an assert in the lib? I could not find one... Mine is costly since it converts values, and we only want to check dimensionality...

arkanoid87 commented 1 year ago

any news on this? I'm playing around trying to make mypy happy with my pint'ed code that works at runtime, but fails in mypy

conversy commented 4 months ago

Might be another instance of 'read the doc, Luke', but I overlooked wrapping:

class Human:
    @ureg.check(None, '[length]')
    def __init__(self, height: pint.Quantity):