summaryrefslogtreecommitdiff
path: root/pint
diff options
context:
space:
mode:
Diffstat (limited to 'pint')
-rw-r--r--pint/compat.py27
-rw-r--r--pint/constants_en.txt2
-rw-r--r--pint/default_en.txt2
-rw-r--r--pint/default_en_0.6.txt360
-rw-r--r--pint/definitions.py16
-rw-r--r--pint/numpy_func.py18
-rwxr-xr-xpint/pint-convert120
-rw-r--r--pint/quantity.py83
-rw-r--r--pint/registry.py101
-rw-r--r--pint/testsuite/helpers.py4
-rw-r--r--pint/testsuite/test_babel.py35
-rw-r--r--pint/testsuite/test_issues.py18
-rw-r--r--pint/testsuite/test_numpy.py38
-rw-r--r--pint/testsuite/test_quantity.py9
-rw-r--r--pint/testsuite/test_unit.py41
-rw-r--r--pint/unit.py35
-rw-r--r--pint/util.py16
17 files changed, 450 insertions, 475 deletions
diff --git a/pint/compat.py b/pint/compat.py
index 6fb27ee..e8e1a1b 100644
--- a/pint/compat.py
+++ b/pint/compat.py
@@ -7,7 +7,6 @@
:copyright: 2013 by Pint Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
-import os
import tokenize
from decimal import Decimal
from io import BytesIO
@@ -37,27 +36,6 @@ class BehaviorChangeWarning(UserWarning):
pass
-array_function_change_msg = """The way Pint handles NumPy operations has changed with the
-implementation of NEP 18. Unimplemented NumPy operations will now fail instead of making
-assumptions about units. Some functions, eg concat, will now return Quanties with units, where
-they returned ndarrays previously. See https://github.com/hgrecco/pint/pull/905.
-
-To hide this warning, wrap your first creation of an array Quantity with
-warnings.catch_warnings(), like the following:
-
-import numpy as np
-import warnings
-from pint import Quantity
-
-with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- Quantity([])
-
-To disable the new behavior, see
-https://www.numpy.org/neps/nep-0018-array-function-protocol.html#implementation
-"""
-
-
try:
import numpy as np
from numpy import ndarray
@@ -93,12 +71,9 @@ try:
return False
HAS_NUMPY_ARRAY_FUNCTION = _test_array_function_protocol()
- SKIP_ARRAY_FUNCTION_CHANGE_WARNING = not HAS_NUMPY_ARRAY_FUNCTION
NP_NO_VALUE = np._NoValue
- ARRAY_FALLBACK = bool(int(os.environ.get("PINT_ARRAY_PROTOCOL_FALLBACK", 1)))
-
except ImportError:
np = None
@@ -110,9 +85,7 @@ except ImportError:
NUMPY_VER = "0"
NUMERIC_TYPES = (Number, Decimal)
HAS_NUMPY_ARRAY_FUNCTION = False
- SKIP_ARRAY_FUNCTION_CHANGE_WARNING = True
NP_NO_VALUE = None
- ARRAY_FALLBACK = False
def _to_magnitude(value, force_ndarray=False, force_ndarray_like=False):
if force_ndarray or force_ndarray_like:
diff --git a/pint/constants_en.txt b/pint/constants_en.txt
index fa485fa..c3ecbf1 100644
--- a/pint/constants_en.txt
+++ b/pint/constants_en.txt
@@ -8,7 +8,7 @@
#### MATHEMATICAL CONSTANTS ####
# As computed by Maxima with fpprec:50
-pi = 3.1415926535897932384626433832795028841971693993751 = π # pi
+pi = 3.1415926535897932384626433832795028841971693993751 = π # pi
tansec = 4.8481368111333441675396429478852851658848753880815e-6 # tangent of 1 arc-second ~ arc_second/radian
ln10 = 2.3025850929940456840179914546843642076011014886288 # natural logarithm of 10
wien_x = 4.9651142317442763036987591313228939440555849867973 # solution to (x-5)*exp(x)+5 = 0 => x = W(5/exp(5))+5
diff --git a/pint/default_en.txt b/pint/default_en.txt
index 6d5fe6a..8bd4133 100644
--- a/pint/default_en.txt
+++ b/pint/default_en.txt
@@ -36,7 +36,7 @@
# [density] = [mass] / [volume]
#
# Note that primary dimensions don't need to be declared; they can be
-# defined or the first time in a unit definition.
+# defined for the first time in a unit definition.
# E.g. see below `meter = [length]`
#
#
diff --git a/pint/default_en_0.6.txt b/pint/default_en_0.6.txt
deleted file mode 100644
index fb722c0..0000000
--- a/pint/default_en_0.6.txt
+++ /dev/null
@@ -1,360 +0,0 @@
-# Default Pint units definition file
-# Based on the International System of Units
-# Language: english
-# :copyright: 2013 by Pint Authors, see AUTHORS for more details.
-
-# decimal prefixes
-yocto- = 1e-24 = y-
-zepto- = 1e-21 = z-
-atto- = 1e-18 = a-
-femto- = 1e-15 = f-
-pico- = 1e-12 = p-
-nano- = 1e-9 = n-
-micro- = 1e-6 = u- = µ-
-milli- = 1e-3 = m-
-centi- = 1e-2 = c-
-deci- = 1e-1 = d-
-deca- = 1e+1 = da-
-hecto- = 1e2 = h-
-kilo- = 1e3 = k-
-mega- = 1e6 = M-
-giga- = 1e9 = G-
-tera- = 1e12 = T-
-peta- = 1e15 = P-
-exa- = 1e18 = E-
-zetta- = 1e21 = Z-
-yotta- = 1e24 = Y-
-
-# binary_prefixes
-kibi- = 2**10 = Ki-
-mebi- = 2**20 = Mi-
-gibi- = 2**30 = Gi-
-tebi- = 2**40 = Ti-
-pebi- = 2**50 = Pi-
-exbi- = 2**60 = Ei-
-zebi- = 2**70 = Zi-
-yobi- = 2**80 = Yi-
-
-# reference
-meter = [length] = m = metre
-second = [time] = s = sec
-ampere = [current] = A = amp
-candela = [luminosity] = cd = candle
-gram = [mass] = g
-mole = [substance] = mol
-kelvin = [temperature]; offset: 0 = K = degK
-radian = [] = rad
-bit = []
-count = []
-
-@import constants_en.txt
-
-# acceleration
-[acceleration] = [length] / [time] ** 2
-
-# Angle
-turn = 2 * pi * radian = revolution = cycle = circle
-degree = pi / 180 * radian = deg = arcdeg = arcdegree = angular_degree
-arcminute = arcdeg / 60 = arcmin = arc_minute = angular_minute
-arcsecond = arcmin / 60 = arcsec = arc_second = angular_second
-steradian = radian ** 2 = sr
-
-# Area
-[area] = [length] ** 2
-are = 100 * m**2
-barn = 1e-28 * m ** 2 = b
-cmil = 5.067075e-10 * m ** 2 = circular_mils
-darcy = 9.869233e-13 * m ** 2
-acre = 4046.8564224 * m ** 2 = international_acre
-hectare = 100 * are = ha
-US_survey_acre = 160 * rod ** 2
-
-# EM
-esu = 1 * erg**0.5 * centimeter**0.5 = statcoulombs = statC = franklin = Fr
-esu_per_second = 1 * esu / second = statampere
-ampere_turn = 1 * A
-gilbert = 10 / (4 * pi ) * ampere_turn
-coulomb = ampere * second = C
-volt = joule / coulomb = V
-farad = coulomb / volt = F
-ohm = volt / ampere = Ω
-siemens = ampere / volt = S = mho
-weber = volt * second = Wb
-tesla = weber / meter ** 2 = T
-henry = weber / ampere = H
-elementary_charge = 1.602176487e-19 * coulomb = e
-chemical_faraday = 9.64957e4 * coulomb
-physical_faraday = 9.65219e4 * coulomb
-faraday = 96485.3399 * coulomb = C12_faraday
-gamma = 1e-9 * tesla
-gauss = 1e-4 * tesla
-maxwell = 1e-8 * weber = mx
-oersted = 1000 / (4 * pi) * A / m = Oe
-statfarad = 1.112650e-12 * farad = statF = stF
-stathenry = 8.987554e11 * henry = statH = stH
-statmho = 1.112650e-12 * siemens = statS = stS
-statohm = 8.987554e11 * ohm
-statvolt = 2.997925e2 * volt = statV = stV
-unit_pole = 1.256637e-7 * weber
-
-# Energy
-[energy] = [force] * [length]
-joule = newton * meter = J
-erg = dyne * centimeter
-btu = 1.05505585262e3 * joule = Btu = BTU = british_thermal_unit
-electron_volt = 1.60217653e-19 * J = eV
-quadrillion_btu = 10**15 * btu = quad
-thm = 100000 * BTU = therm = EC_therm
-cal = 4.184 * joule = calorie = thermochemical_calorie
-international_steam_table_calorie = 4.1868 * joule
-ton_TNT = 4.184e9 * joule = tTNT
-US_therm = 1.054804e8 * joule
-watt_hour = watt * hour = Wh = watthour
-hartree = 4.35974394e-18 * joule = E_h = hartree_energy
-
-# Force
-[force] = [mass] * [acceleration]
-newton = kilogram * meter / second ** 2 = N
-dyne = gram * centimeter / second ** 2 = dyn
-force_kilogram = g_0 * kilogram = kgf = kilogram_force = pond
-force_gram = g_0 * gram = gf = gram_force
-force_ounce = g_0 * ounce = ozf = ounce_force
-force_pound = g_0 * lb = lbf = pound_force
-force_ton = 2000 * force_pound = ton_force
-poundal = lb * feet / second ** 2 = pdl
-kip = 1000*lbf
-
-# Frequency
-[frequency] = 1 / [time]
-hertz = 1 / second = Hz = rps
-revolutions_per_minute = revolution / minute = rpm
-counts_per_second = count / second = cps
-
-# Heat
-#RSI = degK * meter ** 2 / watt
-#clo = 0.155 * RSI = clos
-#R_value = foot ** 2 * degF * hour / btu
-
-# Information
-byte = 8 * bit = B = octet
-baud = bit / second = Bd = bps
-
-# Irradiance
-peak_sun_hour = 1000 * watt_hour / meter**2 = PSH
-langley = thermochemical_calorie / centimeter**2 = Langley
-
-# Length
-angstrom = 1e-10 * meter = Å = ångström = Å
-inch = 2.54 * centimeter = in = international_inch = inches = international_inches
-foot = 12 * inch = ft = international_foot = feet = international_feet
-mile = 5280 * foot = mi = international_mile
-yard = 3 * feet = yd = international_yard
-mil = inch / 1000 = thou
-parsec = 3.08568025e16 * meter = pc
-light_year = speed_of_light * julian_year = ly = lightyear
-astronomical_unit = 149597870691 * meter = au
-nautical_mile = 1.852e3 * meter = nmi
-printers_point = 127 * millimeter / 360 = point
-printers_pica = 12 * printers_point = pica
-US_survey_foot = 1200 * meter / 3937
-US_survey_yard = 3 * US_survey_foot
-US_survey_mile = 5280 * US_survey_foot = US_statute_mile
-rod = 16.5 * US_survey_foot = pole = perch
-furlong = 660 * US_survey_foot
-fathom = 6 * US_survey_foot
-chain = 66 * US_survey_foot
-barleycorn = inch / 3
-arpentlin = 191.835 * feet
-kayser = 1 / centimeter = wavenumber
-
-# Mass
-dram = oz / 16 = dr = avoirdupois_dram
-ounce = 28.349523125 * gram = oz = avoirdupois_ounce
-pound = 0.45359237 * kilogram = lb = avoirdupois_pound
-stone = 14 * lb = st
-carat = 200 * milligram
-grain = 64.79891 * milligram = gr
-long_hundredweight = 112 * lb
-short_hundredweight = 100 * lb
-metric_ton = 1000 * kilogram = t = tonne
-pennyweight = 24 * gram = dwt
-slug = 14.59390 * kilogram
-troy_ounce = 480 * grain = toz = apounce = apothecary_ounce
-troy_pound = 12 * toz = tlb = appound = apothecary_pound
-drachm = 60 * grain = apdram = apothecary_dram
-atomic_mass_unit = 1.660538782e-27 * kilogram = u = amu = dalton = Da
-scruple = 20 * grain
-bag = 94 * lb
-ton = 2000 * lb = short_ton
-
-# Textile
-denier = gram / (9000 * meter)
-tex = gram / (1000 * meter)
-dtex = decitex
-
-# Photometry
-lumen = candela * steradian = lm
-lux = lumen / meter ** 2 = lx
-
-# Power
-[power] = [energy] / [time]
-watt = joule / second = W = volt_ampere = VA
-horsepower = 33000 * ft * lbf / min = hp = UK_horsepower = British_horsepower
-boiler_horsepower = 33475 * btu / hour
-metric_horsepower = 75 * force_kilogram * meter / second
-electric_horsepower = 746 * watt
-hydraulic_horsepower = 550 * feet * lbf / second
-refrigeration_ton = 12000 * btu / hour = ton_of_refrigeration
-
-# Pressure
-[pressure] = [force] / [area]
-Hg = gravity * 13.59510 * gram / centimeter ** 3 = mercury = conventional_mercury
-mercury_60F = gravity * 13.5568 * gram / centimeter ** 3
-H2O = gravity * 1000 * kilogram / meter ** 3 = h2o = water = conventional_water
-water_4C = gravity * 999.972 * kilogram / meter ** 3 = water_39F
-water_60F = gravity * 999.001 * kilogram / m ** 3
-pascal = newton / meter ** 2 = Pa
-bar = 100000 * pascal
-atmosphere = 101325 * pascal = atm = standard_atmosphere
-technical_atmosphere = kilogram * gravity / centimeter ** 2 = at
-torr = atm / 760
-pound_force_per_square_inch = pound * gravity / inch ** 2 = psi
-kip_per_square_inch = kip / inch ** 2 = ksi
-barye = 0.1 * newton / meter ** 2 = barie = barad = barrie = baryd = Ba
-mm_Hg = millimeter * Hg = mmHg = millimeter_Hg = millimeter_Hg_0C
-cm_Hg = centimeter * Hg = cmHg = centimeter_Hg
-in_Hg = inch * Hg = inHg = inch_Hg = inch_Hg_32F
-inch_Hg_60F = inch * mercury_60F
-inch_H2O_39F = inch * water_39F
-inch_H2O_60F = inch * water_60F
-footH2O = ft * water
-cmH2O = centimeter * water
-foot_H2O = ft * water = ftH2O
-standard_liter_per_minute = 1.68875 * Pa * m ** 3 / s = slpm = slm
-
-# Radiation
-Bq = Hz = becquerel
-curie = 3.7e10 * Bq = Ci
-rutherford = 1e6*Bq = rd = Rd
-Gy = joule / kilogram = gray = Sv = sievert
-rem = 1e-2 * sievert
-rads = 1e-2 * gray
-roentgen = 2.58e-4 * coulomb / kilogram
-
-# Temperature
-degC = kelvin; offset: 273.15 = celsius
-degR = 5 / 9 * kelvin; offset: 0 = rankine
-degF = 5 / 9 * kelvin; offset: 255.372222 = fahrenheit
-
-# Time
-minute = 60 * second = min
-hour = 60 * minute = hr
-day = 24 * hour
-week = 7 * day
-fortnight = 2 * week
-year = 31556925.9747 * second
-month = year / 12
-shake = 1e-8 * second
-sidereal_day = day / 1.00273790935079524
-sidereal_hour = sidereal_day / 24
-sidereal_minute = sidereal_hour / 60
-sidereal_second = sidereal_minute / 60
-sidereal_year = 366.25636042 * sidereal_day
-sidereal_month = 27.321661 * sidereal_day
-tropical_month = 27.321661 * day
-synodic_month = 29.530589 * day = lunar_month
-common_year = 365 * day
-leap_year = 366 * day
-julian_year = 365.25 * day
-gregorian_year = 365.2425 * day
-millenium = 1000 * year = millenia = milenia = milenium
-eon = 1e9 * year
-work_year = 2056 * hour
-work_month = work_year / 12
-
-# Velocity
-[speed] = [length] / [time]
-knot = nautical_mile / hour = kt = knot_international = international_knot = nautical_miles_per_hour
-mph = mile / hour = MPH
-kph = kilometer / hour = KPH
-
-# Viscosity
-[viscosity] = [pressure] * [time]
-poise = 1e-1 * Pa * second = P
-stokes = 1e-4 * meter ** 2 / second = St
-rhe = 10 / (Pa * s)
-
-# Volume
-[volume] = [length] ** 3
-liter = 1e-3 * m ** 3 = l = L = litre
-cc = centimeter ** 3 = cubic_centimeter
-stere = meter ** 3
-gross_register_ton = 100 * foot ** 3 = register_ton = GRT
-acre_foot = acre * foot = acre_feet
-board_foot = foot ** 2 * inch = FBM
-bushel = 2150.42 * inch ** 3 = bu = US_bushel
-dry_gallon = bushel / 8 = US_dry_gallon
-dry_quart = dry_gallon / 4 = US_dry_quart
-dry_pint = dry_quart / 2 = US_dry_pint
-gallon = 231 * inch ** 3 = liquid_gallon = US_liquid_gallon
-quart = gallon / 4 = liquid_quart = US_liquid_quart
-pint = quart / 2 = pt = liquid_pint = US_liquid_pint
-cup = pint / 2 = liquid_cup = US_liquid_cup
-gill = cup / 2 = liquid_gill = US_liquid_gill
-fluid_ounce = gill / 4 = floz = US_fluid_ounce = US_liquid_ounce
-imperial_bushel = 36.36872 * liter = UK_bushel
-imperial_gallon = imperial_bushel / 8 = UK_gallon
-imperial_quart = imperial_gallon / 4 = UK_quart
-imperial_pint = imperial_quart / 2 = UK_pint
-imperial_cup = imperial_pint / 2 = UK_cup
-imperial_gill = imperial_cup / 2 = UK_gill
-imperial_floz = imperial_gill / 5 = UK_fluid_ounce = imperial_fluid_ounce
-barrel = 42 * gallon = bbl
-tablespoon = floz / 2 = tbsp = Tbsp = Tblsp = tblsp = tbs = Tbl
-teaspoon = tablespoon / 3 = tsp
-peck = bushel / 4 = pk
-fluid_dram = floz / 8 = fldr = fluidram
-firkin = barrel / 4
-
-
-@context(n=1) spectroscopy = sp
- # n index of refraction of the medium.
- [length] <-> [frequency]: speed_of_light / n / value
- [frequency] -> [energy]: planck_constant * value
- [energy] -> [frequency]: value / planck_constant
-@end
-
-@context boltzmann
- [temperature] -> [energy]: boltzmann_constant * value
- [energy] -> [temperature]: value / boltzmann_constant
-@end
-
-@context(mw=0,volume=0,solvent_mass=0) chemistry = chem
- # mw is the molecular weight of the species
- # volume is the volume of the solution
- # solvent_mass is the mass of solvent in the solution
-
- # moles -> mass require the molecular weight
- [substance] -> [mass]: value * mw
- [mass] -> [substance]: value / mw
-
- # moles/volume -> mass/volume and moles/mass -> mass / mass
- # require the molecular weight
- [substance] / [volume] -> [mass] / [volume]: value * mw
- [mass] / [volume] -> [substance] / [volume]: value / mw
- [substance] / [mass] -> [mass] / [mass]: value * mw
- [mass] / [mass] -> [substance] / [mass]: value / mw
-
- # moles/volume -> moles requires the solution volume
- [substance] / [volume] -> [substance]: value * volume
- [substance] -> [substance] / [volume]: value / volume
-
- # moles/mass -> moles requires the solvent (usually water) mass
- [substance] / [mass] -> [substance]: value * solvent_mass
- [substance] -> [substance] / [mass]: value / solvent_mass
-
- # moles/mass -> moles/volume require the solvent mass and the volume
- [substance] / [mass] -> [substance]/[volume]: value * solvent_mass / volume
- [substance] / [volume] -> [substance] / [mass]: value / solvent_mass * volume
-
-@end
diff --git a/pint/definitions.py b/pint/definitions.py
index 327cc87..338d1fc 100644
--- a/pint/definitions.py
+++ b/pint/definitions.py
@@ -15,8 +15,8 @@ from .errors import DefinitionSyntaxError
from .util import ParserHelper, UnitsContainer, _is_dim
-class ParsedDefinition(
- namedtuple("ParsedDefinition", "name symbol aliases value rhs_parts")
+class PreprocessedDefinition(
+ namedtuple("PreprocessedDefinition", "name symbol aliases value rhs_parts")
):
"""Splits a definition into the constitutive parts.
@@ -126,7 +126,7 @@ class Definition:
Parameters
----------
- definition : str or ParsedDefinition
+ definition : str or PreprocessedDefinition
non_int_type : type
Returns
@@ -135,7 +135,7 @@ class Definition:
"""
if isinstance(definition, str):
- definition = ParsedDefinition.from_string(definition)
+ definition = PreprocessedDefinition.from_string(definition)
if definition.name.startswith("@alias "):
return AliasDefinition.from_string(definition, non_int_type)
@@ -186,7 +186,7 @@ class PrefixDefinition(Definition):
@classmethod
def from_string(cls, definition, non_int_type=float):
if isinstance(definition, str):
- definition = ParsedDefinition.from_string(definition)
+ definition = PreprocessedDefinition.from_string(definition)
aliases = tuple(alias.strip("-") for alias in definition.aliases)
if definition.symbol:
@@ -230,7 +230,7 @@ class UnitDefinition(Definition):
@classmethod
def from_string(cls, definition, non_int_type=float):
if isinstance(definition, str):
- definition = ParsedDefinition.from_string(definition)
+ definition = PreprocessedDefinition.from_string(definition)
if ";" in definition.value:
[converter, modifiers] = definition.value.split(";", 2)
@@ -296,7 +296,7 @@ class DimensionDefinition(Definition):
@classmethod
def from_string(cls, definition, non_int_type=float):
if isinstance(definition, str):
- definition = ParsedDefinition.from_string(definition)
+ definition = PreprocessedDefinition.from_string(definition)
converter = ParserHelper.from_string(definition.value, non_int_type)
@@ -338,7 +338,7 @@ class AliasDefinition(Definition):
def from_string(cls, definition, non_int_type=float):
if isinstance(definition, str):
- definition = ParsedDefinition.from_string(definition)
+ definition = PreprocessedDefinition.from_string(definition)
name = definition.name[len("@alias ") :].lstrip()
return AliasDefinition(name, tuple(definition.rhs_parts))
diff --git a/pint/numpy_func.py b/pint/numpy_func.py
index 1fd7d8c..c03887b 100644
--- a/pint/numpy_func.py
+++ b/pint/numpy_func.py
@@ -654,20 +654,22 @@ def _pad(array, pad_width, mode="constant", **kwargs):
def _recursive_convert(arg, unit):
if iterable(arg):
return tuple(_recursive_convert(a, unit=unit) for a in arg)
- elif _is_quantity(arg):
- return arg.m_as(unit)
- else:
- return arg
+ elif not _is_quantity(arg):
+ if arg == 0 or np.isnan(arg):
+ arg = unit._REGISTRY.Quantity(arg, unit)
+ else:
+ arg = unit._REGISTRY.Quantity(arg, "dimensionless")
+
+ return arg.m_as(unit)
# pad only dispatches on array argument, so we know it is a Quantity
units = array.units
# Handle flexible constant_values and end_values, converting to units if Quantity
# and ignoring if not
- if mode == "constant":
- kwargs["constant_values"] = _recursive_convert(kwargs["constant_values"], units)
- elif mode == "linear_ramp":
- kwargs["end_values"] = _recursive_convert(kwargs["end_values"], units)
+ for key in ("constant_values", "end_values"):
+ if key in kwargs:
+ kwargs[key] = _recursive_convert(kwargs[key], units)
return units._REGISTRY.Quantity(
np.pad(array._magnitude, pad_width, mode=mode, **kwargs), units
diff --git a/pint/pint-convert b/pint/pint-convert
new file mode 100755
index 0000000..da7df23
--- /dev/null
+++ b/pint/pint-convert
@@ -0,0 +1,120 @@
+#!/usr/bin/env python3
+
+"""
+ pint-convert
+ ~~~~~~~~~~~~
+
+ :copyright: 2020 by Pint Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+import argparse
+import re
+import sys
+
+from pint import UnitRegistry
+
+parser = argparse.ArgumentParser(description='Unit converter.', usage=argparse.SUPPRESS)
+parser.add_argument('-s', '--system', metavar='sys', default='SI', help='unit system to convert to (default: SI)')
+parser.add_argument('-p', '--prec', metavar='n', type=int, default=12, help='number of maximum significant figures (default: 12)')
+parser.add_argument('-u', '--prec-unc', metavar='n', type=int, default=2, help='number of maximum uncertainty digits (default: 2)')
+parser.add_argument('-U', '--no-unc', dest='unc', action='store_false', help='ignore uncertainties in constants')
+parser.add_argument('-C', '--no-corr', dest='corr', action='store_false', help='ignore correlations between constants')
+parser.add_argument('fr', metavar='from', type=str, help='unit or quantity to convert from')
+parser.add_argument('to', type=str, nargs='?', help='unit to convert to')
+try:
+ args = parser.parse_args()
+except SystemExit:
+ parser.print_help()
+ raise
+
+ureg = UnitRegistry()
+ureg.auto_reduce_dimensions = True
+ureg.autoconvert_offset_to_baseunit = True
+ureg.enable_contexts('Gau', 'ESU', 'sp', 'energy', 'boltzmann')
+ureg.default_system = args.system
+
+if args.unc:
+ import uncertainties
+ # Measured constans subject to correlation
+ # R_i: Rydberg constant
+ # g_e: Electron g factor
+ # m_u: Atomic mass constant
+ # m_e: Electron mass
+ # m_p: Proton mass
+ # m_n: Neutron mass
+ R_i = (ureg._units['R_inf'].converter.scale, 0.0000000000021e7)
+ g_e = (ureg._units['g_e'].converter.scale, 0.00000000000035)
+ m_u = (ureg._units['m_u'].converter.scale, 0.00000000050e-27)
+ m_e = (ureg._units['m_e'].converter.scale, 0.00000000028e-30)
+ m_p = (ureg._units['m_p'].converter.scale, 0.00000000051e-27)
+ m_n = (ureg._units['m_n'].converter.scale, 0.00000000095e-27)
+ if args.corr:
+ # Correlation matrix between measured constants (to be completed below)
+ # R_i g_e m_u m_e m_p m_n
+ corr = [[ 1.0 , -0.00206, 0.00369, 0.00436, 0.00194, 0.00233], # R_i
+ [ -0.00206, 1.0 , 0.99029, 0.99490, 0.97560, 0.52445], # g_e
+ [ 0.00369, 0.99029, 1.0 , 0.99536, 0.98516, 0.52959], # m_u
+ [ 0.00436, 0.99490, 0.99536, 1.0 , 0.98058, 0.52714], # m_e
+ [ 0.00194, 0.97560, 0.98516, 0.98058, 1.0 , 0.51521], # m_p
+ [ 0.00233, 0.52445, 0.52959, 0.52714, 0.51521, 1.0 ]] # m_n
+ (R_i, g_e, m_u, m_e, m_p, m_n) = uncertainties.correlated_values_norm([R_i, g_e, m_u, m_e, m_p, m_n], corr)
+ else:
+ R_i = uncertainties.ufloat(*R_i)
+ g_e = uncertainties.ufloat(*g_e)
+ m_u = uncertainties.ufloat(*m_u)
+ m_e = uncertainties.ufloat(*m_e)
+ m_p = uncertainties.ufloat(*m_p)
+ m_n = uncertainties.ufloat(*m_n)
+ ureg._units['R_inf'].converter.scale = R_i
+ ureg._units['g_e'].converter.scale = g_e
+ ureg._units['m_u'].converter.scale = m_u
+ ureg._units['m_e'].converter.scale = m_e
+ ureg._units['m_p'].converter.scale = m_p
+ ureg._units['m_n'].converter.scale = m_n
+
+ # Measured constants with zero correlation
+ ureg._units['gravitational_constant'].converter.scale = uncertainties.ufloat(ureg._units['gravitational_constant'].converter.scale, 0.00015e-11)
+ ureg._units['d_220'].converter.scale = uncertainties.ufloat(ureg._units['d_220'].converter.scale, 0.000000032e-10)
+ ureg._units['K_alpha_Cu_d_220'].converter.scale = uncertainties.ufloat(ureg._units['K_alpha_Cu_d_220'].converter.scale, 0.00000022)
+ ureg._units['K_alpha_Mo_d_220'].converter.scale = uncertainties.ufloat(ureg._units['K_alpha_Mo_d_220'].converter.scale, 0.00000019)
+ ureg._units['K_alpha_W_d_220'].converter.scale = uncertainties.ufloat(ureg._units['K_alpha_W_d_220'].converter.scale, 0.000000098)
+
+ ureg._root_units_cache = dict()
+ ureg._build_cache()
+
+def convert(u_from, u_to=None, unc=None, factor=None):
+ q = ureg.Quantity(u_from)
+ fmt = '.{}g'.format(args.prec)
+ if unc:
+ q = q.plus_minus(unc)
+ if u_to:
+ nq = q.to(u_to)
+ else:
+ nq = q.to_base_units()
+ if (factor):
+ q *= ureg.Quantity(factor)
+ nq *= ureg.Quantity(factor).to_base_units()
+ prec_unc = use_unc(nq.magnitude, fmt, args.prec_unc)
+ if (prec_unc > 0):
+ fmt = '.{}uS'.format(prec_unc)
+ else:
+ try:
+ nq = nq.magnitude.n * nq.units
+ except:
+ pass
+ fmt = '{:' + fmt + '} {:~P}'
+ print(('{:} = ' + fmt).format(q, nq.magnitude, nq.units))
+
+def use_unc(num, fmt, prec_unc):
+ unc = 0
+ try:
+ if (isinstance(num, uncertainties.UFloat)):
+ full = ('{:'+fmt+'}').format(num)
+ unc = re.search(r'\+\/-[0.]*([\d.]*)', full).group(1)
+ unc = len(unc.replace('.', ''))
+ except:
+ pass
+ return max(0, min(prec_unc, unc))
+
+convert(args.fr, args.to)
diff --git a/pint/quantity.py b/pint/quantity.py
index 1d9ed21..d434a0f 100644
--- a/pint/quantity.py
+++ b/pint/quantity.py
@@ -20,13 +20,9 @@ import warnings
from pkg_resources.extern.packaging import version
-from .compat import SKIP_ARRAY_FUNCTION_CHANGE_WARNING # noqa: F401
from .compat import (
- ARRAY_FALLBACK,
NUMPY_VER,
- BehaviorChangeWarning,
_to_magnitude,
- array_function_change_msg,
babel_parse,
eq,
is_duck_array_type,
@@ -165,8 +161,6 @@ class Quantity(PrettyIPython, SharedRegistryObject):
return _unpickle, (Quantity, self.magnitude, self._units)
def __new__(cls, value, units=None):
- global SKIP_ARRAY_FUNCTION_CHANGE_WARNING
-
if is_upcast_type(type(value)):
raise TypeError(f"Quantity cannot wrap upcast type {type(value)}")
elif units is None:
@@ -219,12 +213,6 @@ class Quantity(PrettyIPython, SharedRegistryObject):
inst.__used = False
inst.__handling = None
- if not SKIP_ARRAY_FUNCTION_CHANGE_WARNING and isinstance(
- inst._magnitude, ndarray
- ):
- warnings.warn(array_function_change_msg, BehaviorChangeWarning)
- SKIP_ARRAY_FUNCTION_CHANGE_WARNING = True
-
return inst
@property
@@ -256,6 +244,9 @@ class Quantity(PrettyIPython, SharedRegistryObject):
return ret
def __str__(self):
+ if self._REGISTRY.fmt_locale is not None:
+ return self.format_babel()
+
return format(self)
def __bytes__(self):
@@ -274,6 +265,9 @@ class Quantity(PrettyIPython, SharedRegistryObject):
_exp_pattern = re.compile(r"([0-9]\.?[0-9]*)e(-?)\+?0*([0-9]+)")
def __format__(self, spec):
+ if self._REGISTRY.fmt_locale is not None:
+ return self.format_babel(spec)
+
spec = spec or self.default_format
if "L" in spec:
@@ -508,6 +502,40 @@ class Quantity(PrettyIPython, SharedRegistryObject):
return self._REGISTRY.get_compatible_units(self._units)
+ def is_compatible_with(self, other, *contexts, **ctx_kwargs):
+ """ check if the other object is compatible
+
+ Parameters
+ ----------
+ other
+ The object to check. Treated as dimensionless if not a
+ Quantity, Unit or str.
+ *contexts : str or pint.Context
+ Contexts to use in the transformation.
+ **ctx_kwargs :
+ Values for the Context/s
+
+ Returns
+ -------
+ bool
+ """
+ if contexts:
+ try:
+ self.to(other, *contexts, **ctx_kwargs)
+ return True
+ except DimensionalityError:
+ return False
+
+ if isinstance(other, (self._REGISTRY.Quantity, self._REGISTRY.Unit)):
+ return self.dimensionality == other.dimensionality
+
+ if isinstance(other, str):
+ return (
+ self.dimensionality == self._REGISTRY.parse_units(other).dimensionality
+ )
+
+ return self.dimensionless
+
def _convert_magnitude_not_inplace(self, other, *contexts, **ctx_kwargs):
if contexts:
with self._REGISTRY.context(*contexts, **ctx_kwargs):
@@ -693,6 +721,8 @@ class Quantity(PrettyIPython, SharedRegistryObject):
if unit is None:
unit = infer_base_unit(self)
+ else:
+ unit = infer_base_unit(self.__class__(1, unit))
q_base = self.to(unit)
@@ -1665,34 +1695,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
def __getattr__(self, item):
if item.startswith("__array_"):
# Handle array protocol attributes other than `__array__`
- if ARRAY_FALLBACK:
- # Deprecated fallback behavior
- warnings.warn(
- (
- f"Array protocol attribute {item} accessed, with unit of the "
- "Quantity being stripped. This attribute will become unavailable "
- "in the next minor version of Pint. To make this potentially "
- "incorrect attribute unavailable now, set the "
- "PINT_ARRAY_PROTOCOL_FALLBACK environment variable to 0 before "
- "importing Pint."
- ),
- DeprecationWarning,
- stacklevel=2,
- )
-
- if is_duck_array_type(type(self._magnitude)):
- # Defer to magnitude, and don't catch any AttributeErrors
- return getattr(self._magnitude, item)
- else:
- # If an `__array_` attribute is requested but the magnitude is not
- # a duck array, we convert the magnitude to a numpy ndarray.
- magnitude_as_array = _to_magnitude(
- self._magnitude, force_ndarray=True
- )
- return getattr(magnitude_as_array, item)
- else:
- # TODO (next minor version): ARRAY_FALLBACK is removed and this becomes the standard behavior
- raise AttributeError(f"Array protocol attribute {item} not available.")
+ raise AttributeError(f"Array protocol attribute {item} not available.")
elif item in HANDLED_UFUNCS or item in self._wrapped_numpy_methods:
magnitude_as_duck_array = _to_magnitude(
self._magnitude, force_ndarray_like=True
diff --git a/pint/registry.py b/pint/registry.py
index 9379770..99873ed 100644
--- a/pint/registry.py
+++ b/pint/registry.py
@@ -84,6 +84,19 @@ from .util import (
_BLOCK_RE = re.compile(r" |\(")
+@functools.lru_cache()
+def pattern_to_regex(pattern):
+ if hasattr(pattern, "finditer"):
+ pattern = pattern.pattern
+
+ # Replace "{unit_name}" match string with float regex with unit_name as group
+ pattern = re.sub(
+ r"{(\w+)}", r"(?P<\1>[+-]?[0-9]+(?:.[0-9]+)?(?:[Ee][+-]?[0-9]+)?)", pattern
+ )
+
+ return re.compile(pattern)
+
+
class RegistryMeta(type):
"""This is just to call after_init at the right time
instead of asking the developer to do it when subclassing.
@@ -161,6 +174,9 @@ class BaseRegistry(metaclass=RegistryMeta):
#: type: Dict[str, (SourceIterator -> None)]
_parsers = None
+ #: Babel.Locale instance or None
+ fmt_locale = None
+
#: List to be used in addition of units when dir(registry) is called.
#: Also used for autocompletion in IPython.
_dir = [
@@ -207,7 +223,7 @@ class BaseRegistry(metaclass=RegistryMeta):
self.auto_reduce_dimensions = auto_reduce_dimensions
#: Default locale identifier string, used when calling format_babel without explicit locale.
- self.fmt_locale = self.set_fmt_locale(fmt_locale)
+ self.set_fmt_locale(fmt_locale)
#: Numerical type used for non integer values.
self.non_int_type = non_int_type
@@ -871,6 +887,33 @@ class BaseRegistry(metaclass=RegistryMeta):
src_dim = self._get_dimensionality(input_units)
return self._cache.dimensional_equivalents[src_dim]
+ def is_compatible_with(self, obj1, obj2, *contexts, **ctx_kwargs):
+ """ check if the other object is compatible
+
+ Parameters
+ ----------
+ obj1, obj2
+ The objects to check against each other. Treated as
+ dimensionless if not a Quantity, Unit or str.
+ *contexts : str or pint.Context
+ Contexts to use in the transformation.
+ **ctx_kwargs :
+ Values for the Context/s
+
+ Returns
+ -------
+ bool
+ """
+ if isinstance(obj1, (self.Quantity, self.Unit)):
+ return obj1.is_compatible_with(obj2, *contexts, **ctx_kwargs)
+
+ if isinstance(obj1, str):
+ return self.parse_expression(obj1).is_compatible_with(
+ obj2, *contexts, **ctx_kwargs
+ )
+
+ return not isinstance(obj2, (self.Quantity, self.Unit))
+
def convert(self, value, src, dst, inplace=False):
"""Convert value from some source to destination units.
@@ -1114,6 +1157,62 @@ class BaseRegistry(metaclass=RegistryMeta):
else:
raise Exception("unknown token type")
+ def parse_pattern(
+ self, input_string, pattern, case_sensitive=True, use_decimal=False, many=False
+ ):
+ """Parse a string with a given regex pattern and returns result.
+
+ Parameters
+ ----------
+ input_string :
+
+ pattern_string:
+ The regex parse string
+ case_sensitive :
+ (Default value = True)
+ use_decimal :
+ (Default value = False)
+ many :
+ Match many results
+ (Default value = False)
+
+
+ Returns
+ -------
+
+ """
+
+ if not input_string:
+ return self.Quantity(1)
+
+ # Parse string
+ pattern = pattern_to_regex(pattern)
+ matched = re.finditer(pattern, input_string)
+
+ # Extract result(s)
+ results = []
+ for match in matched:
+ # Extract units from result
+ match = match.groupdict()
+
+ # Parse units
+ units = []
+ for unit, value in match.items():
+ # Construct measure by multiplying value by unit
+ units.append(
+ float(value)
+ * self.parse_expression(unit, case_sensitive, use_decimal)
+ )
+
+ # Add to results
+ results.append(units)
+
+ # Return first match only
+ if not many:
+ return results[0]
+
+ return results
+
def parse_expression(
self, input_string, case_sensitive=True, use_decimal=False, **values,
):
diff --git a/pint/testsuite/helpers.py b/pint/testsuite/helpers.py
index 5a91391..2bf743c 100644
--- a/pint/testsuite/helpers.py
+++ b/pint/testsuite/helpers.py
@@ -67,6 +67,10 @@ def requires_babel():
return unittest.skipUnless(HAS_BABEL, "Requires Babel with units support")
+def requires_not_babel():
+ return unittest.skipIf(HAS_BABEL, "Requires Babel is not installed")
+
+
def requires_uncertainties():
return unittest.skipUnless(HAS_UNCERTAINTIES, "Requires Uncertainties")
diff --git a/pint/testsuite/test_babel.py b/pint/testsuite/test_babel.py
index 2aac0cd..def7c83 100644
--- a/pint/testsuite/test_babel.py
+++ b/pint/testsuite/test_babel.py
@@ -5,6 +5,14 @@ from pint.testsuite import BaseTestCase, helpers
class TestBabel(BaseTestCase):
+ @helpers.requires_not_babel()
+ def test_no_babel(self):
+ ureg = UnitRegistry()
+ distance = 24.0 * ureg.meter
+ self.assertRaises(
+ Exception, distance.format_babel, locale="fr_FR", length="long"
+ )
+
@helpers.requires_babel()
def test_format(self):
ureg = UnitRegistry()
@@ -46,9 +54,32 @@ class TestBabel(BaseTestCase):
mks = ureg.get_system("mks")
self.assertEqual(mks.format_babel(locale="fr_FR"), "métrique")
- def test_nobabel(self):
+ @helpers.requires_babel()
+ def test_no_registry_locale(self):
ureg = UnitRegistry()
distance = 24.0 * ureg.meter
self.assertRaises(
- Exception, distance.format_babel, locale="fr_FR", length="long"
+ Exception, distance.format_babel,
)
+
+ @helpers.requires_babel()
+ def test_str(self):
+ ureg = UnitRegistry()
+ d = 24.0 * ureg.meter
+
+ s = "24.0 meter"
+ self.assertEqual(str(d), s)
+ self.assertEqual("%s" % d, s)
+ self.assertEqual("{}".format(d), s)
+
+ ureg.set_fmt_locale("fr_FR")
+ s = "24.0 mètres"
+ self.assertEqual(str(d), s)
+ self.assertEqual("%s" % d, s)
+ self.assertEqual("{}".format(d), s)
+
+ ureg.set_fmt_locale(None)
+ s = "24.0 meter"
+ self.assertEqual(str(d), s)
+ self.assertEqual("%s" % d, s)
+ self.assertEqual("{}".format(d), s)
diff --git a/pint/testsuite/test_issues.py b/pint/testsuite/test_issues.py
index 3036022..5f42717 100644
--- a/pint/testsuite/test_issues.py
+++ b/pint/testsuite/test_issues.py
@@ -697,6 +697,24 @@ class TestIssues(QuantityTestCase):
with self.assertRaises(DimensionalityError):
q.to("joule")
+ def test_issue507(self):
+ # leading underscore in unit works with numbers
+ ureg.define("_100km = 100 * kilometer")
+ battery_ec = 16 * ureg.kWh / ureg._100km # noqa: F841
+ # ... but not with text
+ ureg.define("_home = 4700 * kWh / year")
+ with self.assertRaises(AttributeError):
+ home_elec_power = 1 * ureg._home # noqa: F841
+ # ... or with *only* underscores
+ ureg.define("_ = 45 * km")
+ with self.assertRaises(AttributeError):
+ one_blank = 1 * ureg._ # noqa: F841
+
+ def test_issue960(self):
+ q = (1 * ureg.nanometer).to_compact("micrometer")
+ assert q.units == ureg.nanometer
+ assert q.magnitude == 1
+
try:
diff --git a/pint/testsuite/test_numpy.py b/pint/testsuite/test_numpy.py
index 0efb476..8e3d116 100644
--- a/pint/testsuite/test_numpy.py
+++ b/pint/testsuite/test_numpy.py
@@ -1,7 +1,6 @@
import copy
import operator as op
import unittest
-from unittest.mock import patch
from pint import DimensionalityError, OffsetUnitCalculusError, UnitStrippedWarning
from pint.compat import np
@@ -885,7 +884,7 @@ class TestNumpyUnclassified(TestNumpyMethods):
@helpers.requires_array_function_protocol()
def test_result_type_numpy_func(self):
- self.assertEqual(np.result_type(self.q), np.dtype("int64"))
+ self.assertEqual(np.result_type(self.q), np.dtype("int"))
@helpers.requires_array_function_protocol()
def test_nan_to_num_numpy_func(self):
@@ -1031,28 +1030,15 @@ class TestNumpyUnclassified(TestNumpyMethods):
np.array([[1, 0, 2], [3, 0, 4]]) * self.ureg.m,
)
- @patch("pint.quantity.ARRAY_FALLBACK", False)
def test_ndarray_downcast(self):
with self.assertWarns(UnitStrippedWarning):
np.asarray(self.q)
- @patch("pint.quantity.ARRAY_FALLBACK", False)
def test_ndarray_downcast_with_dtype(self):
with self.assertWarns(UnitStrippedWarning):
qarr = np.asarray(self.q, dtype=np.float64)
self.assertEqual(qarr.dtype, np.float64)
- def test_array_protocol_fallback(self):
- with self.assertWarns(DeprecationWarning) as cm:
- for attr in ("__array_struct__", "__array_interface__"):
- getattr(self.q, attr)
- warning_text = str(cm.warnings[0].message)
- self.assertTrue(
- f"unit of the Quantity being stripped" in warning_text
- and "will become unavailable" in warning_text
- )
-
- @patch("pint.quantity.ARRAY_FALLBACK", False)
def test_array_protocol_unavailable(self):
for attr in ("__array_struct__", "__array_interface__"):
self.assertRaises(AttributeError, getattr, self.q, attr)
@@ -1067,14 +1053,32 @@ class TestNumpyUnclassified(TestNumpyMethods):
def test_pad(self):
# Tests reproduced with modification from NumPy documentation
a = [1, 2, 3, 4, 5] * self.ureg.m
+ b = self.Q_([4.0, 6.0, 8.0, 9.0, -3.0], "degC")
+
+ self.assertQuantityEqual(
+ np.pad(a, (2, 3), "constant"), [0, 0, 1, 2, 3, 4, 5, 0, 0, 0] * self.ureg.m,
+ )
+ self.assertQuantityEqual(
+ np.pad(a, (2, 3), "constant", constant_values=(0, 600 * self.ureg.cm)),
+ [0, 0, 1, 2, 3, 4, 5, 6, 6, 6] * self.ureg.m,
+ )
self.assertQuantityEqual(
- np.pad(a, (2, 3), "constant", constant_values=(4, 600 * self.ureg.cm)),
- [4, 4, 1, 2, 3, 4, 5, 6, 6, 6] * self.ureg.m,
+ np.pad(
+ b, (2, 1), "constant", constant_values=(np.nan, self.Q_(10, "degC"))
+ ),
+ self.Q_([np.nan, np.nan, 4, 6, 8, 9, -3, 10], "degC"),
+ )
+ self.assertRaises(
+ DimensionalityError, np.pad, a, (2, 3), "constant", constant_values=4
)
self.assertQuantityEqual(
np.pad(a, (2, 3), "edge"), [1, 1, 1, 2, 3, 4, 5, 5, 5, 5] * self.ureg.m
)
self.assertQuantityEqual(
+ np.pad(a, (2, 3), "linear_ramp"),
+ [0, 0, 1, 2, 3, 4, 5, 3, 1, 0] * self.ureg.m,
+ )
+ self.assertQuantityEqual(
np.pad(a, (2, 3), "linear_ramp", end_values=(5, -4) * self.ureg.m),
[5, 3, 1, 2, 3, 4, 5, 2, -1, -4] * self.ureg.m,
)
diff --git a/pint/testsuite/test_quantity.py b/pint/testsuite/test_quantity.py
index fbfd773..51faf1a 100644
--- a/pint/testsuite/test_quantity.py
+++ b/pint/testsuite/test_quantity.py
@@ -6,7 +6,7 @@ import warnings
from unittest.mock import patch
from pint import DimensionalityError, OffsetUnitCalculusError, UnitRegistry
-from pint.compat import BehaviorChangeWarning, np
+from pint.compat import np
from pint.testsuite import QuantityTestCase, helpers
from pint.testsuite.parameterized import ParameterizedTestCase
from pint.unit import UnitsContainer
@@ -531,11 +531,8 @@ class TestQuantity(QuantityTestCase):
iter(x)
@helpers.requires_array_function_protocol()
- @patch("pint.quantity.SKIP_ARRAY_FUNCTION_CHANGE_WARNING", False)
- def test_array_function_warning_on_creation(self):
- # Test that warning is raised on first creation, but not second
- with self.assertWarns(BehaviorChangeWarning):
- self.Q_([])
+ def test_no_longer_array_function_warning_on_creation(self):
+ # Test that warning is no longer raised on first creation
with warnings.catch_warnings():
warnings.filterwarnings("error")
self.Q_([])
diff --git a/pint/testsuite/test_unit.py b/pint/testsuite/test_unit.py
index 0b6c086..d7eb649 100644
--- a/pint/testsuite/test_unit.py
+++ b/pint/testsuite/test_unit.py
@@ -658,6 +658,47 @@ class TestRegistry(QuantityTestCase):
self.assertEqual(ureg.parse_units(""), ureg.Unit(""))
self.assertRaises(ValueError, ureg.parse_units, "2 * meter")
+ def test_parse_string_pattern(self):
+ ureg = self.ureg
+ self.assertEqual(
+ ureg.parse_pattern("10'11", r"{foot}'{inch}"),
+ [ureg.Quantity(10.0, "foot"), ureg.Quantity(11.0, "inch")],
+ )
+
+ def test_parse_string_pattern_no_preprocess(self):
+ """Were preprocessors enabled, this would be interpreted as 10*11, not
+ two separate units, and thus cause the parsing to fail"""
+ ureg = self.ureg
+ self.assertEqual(
+ ureg.parse_pattern("10 11", r"{kg} {lb}"),
+ [ureg.Quantity(10.0, "kilogram"), ureg.Quantity(11.0, "pound")],
+ )
+
+ def test_parse_pattern_many_results(self):
+ ureg = self.ureg
+ self.assertEqual(
+ ureg.parse_pattern(
+ "1.5kg or 2kg will be fine, if you do not have 3kg",
+ r"{kg}kg",
+ many=True,
+ ),
+ [
+ [ureg.Quantity(1.5, "kilogram")],
+ [ureg.Quantity(2.0, "kilogram")],
+ [ureg.Quantity(3.0, "kilogram")],
+ ],
+ )
+
+ def test_parse_pattern_many_results_two_units(self):
+ ureg = self.ureg
+ self.assertEqual(
+ ureg.parse_pattern("10'10 or 10'11", "{foot}'{inch}", many=True),
+ [
+ [ureg.Quantity(10.0, "foot"), ureg.Quantity(10.0, "inch")],
+ [ureg.Quantity(10.0, "foot"), ureg.Quantity(11.0, "inch")],
+ ],
+ )
+
class TestCompatibleUnits(QuantityTestCase):
FORCE_NDARRAY = False
diff --git a/pint/unit.py b/pint/unit.py
index 5db27cd..25084b4 100644
--- a/pint/unit.py
+++ b/pint/unit.py
@@ -15,6 +15,7 @@ from numbers import Number
from .compat import NUMERIC_TYPES, is_upcast_type
from .definitions import UnitDefinition
+from .errors import DimensionalityError
from .formatting import siunitx_format_unit
from .util import PrettyIPython, SharedRegistryObject, UnitsContainer
@@ -143,6 +144,40 @@ class Unit(PrettyIPython, SharedRegistryObject):
return self._REGISTRY.get_compatible_units(self)
+ def is_compatible_with(self, other, *contexts, **ctx_kwargs):
+ """ check if the other object is compatible
+
+ Parameters
+ ----------
+ other
+ The object to check. Treated as dimensionless if not a
+ Quantity, Unit or str.
+ *contexts : str or pint.Context
+ Contexts to use in the transformation.
+ **ctx_kwargs :
+ Values for the Context/s
+
+ Returns
+ -------
+ bool
+ """
+ if contexts:
+ try:
+ (1 * self).to(other, *contexts, **ctx_kwargs)
+ return True
+ except DimensionalityError:
+ return False
+
+ if isinstance(other, (self._REGISTRY.Quantity, self._REGISTRY.Unit)):
+ return self.dimensionality == other.dimensionality
+
+ if isinstance(other, str):
+ return (
+ self.dimensionality == self._REGISTRY.parse_units(other).dimensionality
+ )
+
+ return self.dimensionless
+
def __mul__(self, other):
if self._check(other):
if isinstance(other, self.__class__):
diff --git a/pint/util.py b/pint/util.py
index 68bed2e..aeebff1 100644
--- a/pint/util.py
+++ b/pint/util.py
@@ -897,12 +897,16 @@ def infer_base_unit(q):
def getattr_maybe_raise(self, item):
- """Helper function to invoke at the beginning of all overridden ``__getattr__``
- methods. Raise AttributeError if the user tries to ask for a _ or __ attribute.
+ """Helper function invoked at start of all overridden ``__getattr__``.
+
+ Raise AttributeError if the user tries to ask for a _ or __ attribute,
+ *unless* it is immediately followed by a number, to enable units
+ encompassing constants, such as ``L / _100km``.
Parameters
----------
- item :
+ item : string
+ Item to be found.
Returns
@@ -911,7 +915,11 @@ def getattr_maybe_raise(self, item):
"""
# Double-underscore attributes are tricky to detect because they are
# automatically prefixed with the class name - which may be a subclass of self
- if item.startswith("_") or item.endswith("__"):
+ if (
+ item.endswith("__")
+ or len(item.lstrip("_")) == 0
+ or (item.startswith("_") and not item.lstrip("_")[0].isdigit())
+ ):
raise AttributeError("%r object has no attribute %r" % (self, item))