Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
183 views
in Technique[技术] by (71.8m points)

python - Get base unit of a custom dimension

Say I define a new dimension and define new units along that dimension. In this example, I use currencies and made-up exchange rates, but could be any other custom-made dimension:

import pint 
ureg = pint.UnitRegistry()
Q_ = ureg.Quantity
import io
ctx_def = io.StringIO("""
EUR = [currency]
DKK = 0.14 EUR
JPY = 0.01 EUR
USD = 0.9 EUR
GBP = 1.1 EUR
""")
ureg.load_definitions(ctx_def)

Here, EUR is the base unit, and conversion to this base unit works fine:

Q_(42, "JPY").to_base_units()
# returns 0.42 EUR as expected

My question is: Given this unit registry, and given the custom dimension name as input i.e. "[currency]", how do I get the base unit "EUR"?


If it's a built-in dimension such as [mass], then I can do this (not elegant, but works):

ureg.get_base_units(list(ureg.get_compatible_units("[mass]"))[0])[1]
# returns "kilogram"

However, this trick doesn't work for my custom-made dimension [currency]:

ureg.get_base_units(list(ureg.get_compatible_units("[currency]"))[0])[1]
# raises:
# KeyError: <UnitsContainer({'[currency]': 1})>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

One option would be to just look up the dimensionality for all contained units and take the first one that matches:

from pint.registry import UnitsContainer

def root_unit(dim):
    uc = UnitsContainer({dim: 1})
    return next(ureg.get_root_units(unit)[1] for unit in ureg if ureg.get_dimensionality(unit) == uc)

print(root_unit('[currency]'))  # EUR
print(root_unit('[mass]'))      # gram

Note also that units are mapped to dimensions in the underlying RegistryCache, stored as ureg._cache. So if you don't mind relying on private members, you can do the lookup there:

In [14]: [k for k, v in ureg._cache.dimensionality.items() if v == UnitsContainer({'[currency]': 1})]
Out[14]:
[<ParserHelper(1, {'EUR': 1})>,
 <ParserHelper(1, {'USD': 1})>,
 <ParserHelper(1, {'DKK': 1})>,
 <ParserHelper(1, {'JPY': 1})>,
 <ParserHelper(1, {'GBP': 1})>]

In particular, turning [currency] into EUR amounts to

def root_unit(dim):
    uc = UnitsContainer({dim: 1})
    return next(ureg.get_root_units(k)[1] for k, v in ureg._cache.dimensionality.items() if v == uc)

ureg._build_cache()
print(root_unit('[currency]'))  # EUR
print(root_unit('[mass]'))      # gram

This would all be a bit more straightforward if the dimensionality itself were a key in RegistryCache.root_units but still, could be worse.

Note the necessary ureg._build_cache(), though: The cache is built only before you load your definition. A priori, this is fair enough since it's up to UnitRegistry itself how it wants to cache its lookup, and it shouldn't be a problem in practice since it only fails because you're loading your own definition, and once you're loading your own definition, well, you already have the information you're looking for in the definition itself!

However, there does seem to be a bug in this case, as the return values of exposed methods end up depending on the registry's internal state:

In [26]: ureg.get_compatible_units('EUR')  # Fails
---------------------------------------------------------------------------
KeyError

In [27]: ureg._build_cache()

In [28]: ureg.get_compatible_units('EUR')  # No longer fails
Out[28]: frozenset()

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...