From 3538fdcaa48d85faddb276a63e62f044d62b8368 Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Sat, 15 Jul 2023 13:18:41 -0300 Subject: [PATCH] Add cache to parse_unit_name --- pint/compat.py | 24 ++++++++++++++++++ pint/facets/context/registry.py | 11 +++++++-- pint/facets/plain/__init__.py | 9 ++++++- pint/facets/plain/registry.py | 44 +++++++++++++++++++++++++++------ 4 files changed, 77 insertions(+), 11 deletions(-) diff --git a/pint/compat.py b/pint/compat.py index 6be906f4d..a67bf20a7 100644 --- a/pint/compat.py +++ b/pint/compat.py @@ -14,12 +14,14 @@ import math import tokenize from decimal import Decimal +import functools from importlib import import_module from io import BytesIO from numbers import Number from collections.abc import Mapping from typing import Any, NoReturn, Callable, Optional, Union from collections.abc import Generator, Iterable +import warnings if sys.version_info >= (3, 10): @@ -362,3 +364,25 @@ def zero_or_nan(obj: Any, check_all: bool) -> Union[bool, Iterable[bool]]: if check_all and is_duck_array_type(type(out)): return out.all() return out + + +def deprecated(msg: str): + def _inner(func: Callable[..., Any]): + """This is a decorator which can be used to mark functions + as deprecated. It will result in a warning being emitted + when the function is used.""" + + @functools.wraps(func) + def _new_func(*args: Any, **kwargs: Any): + warnings.simplefilter("always", DeprecationWarning) # turn off filter + warnings.warn( + f"Call to deprecated function {func.__name__}.\n{msg}", + category=DeprecationWarning, + stacklevel=2, + ) + warnings.simplefilter("default", DeprecationWarning) # reset filter + return func(*args, **kwargs) + + return _new_func + + return _inner diff --git a/pint/facets/context/registry.py b/pint/facets/context/registry.py index 85682d198..18a024077 100644 --- a/pint/facets/context/registry.py +++ b/pint/facets/context/registry.py @@ -17,7 +17,13 @@ from ..._typing import F, Magnitude from ...errors import UndefinedUnitError from ...util import find_connected_nodes, find_shortest_path, logger, UnitsContainer -from ..plain import GenericPlainRegistry, UnitDefinition, QuantityT, UnitT +from ..plain import ( + GenericPlainRegistry, + UnitDefinition, + QuantityT, + UnitT, + RegistryCache, +) from .definitions import ContextDefinition from . import objects @@ -30,11 +36,12 @@ class ContextCacheOverlay: active contexts which contain unit redefinitions. """ - def __init__(self, registry_cache) -> None: + def __init__(self, registry_cache: RegistryCache) -> None: self.dimensional_equivalents = registry_cache.dimensional_equivalents self.root_units = {} self.dimensionality = registry_cache.dimensionality self.parse_unit = registry_cache.parse_unit + self.parse_unit_name = registry_cache.parse_unit_name class GenericContextRegistry( diff --git a/pint/facets/plain/__init__.py b/pint/facets/plain/__init__.py index 90bf2e35a..742ee3cf9 100644 --- a/pint/facets/plain/__init__.py +++ b/pint/facets/plain/__init__.py @@ -19,7 +19,13 @@ UnitDefinition, ) from .objects import PlainQuantity, PlainUnit -from .registry import PlainRegistry, GenericPlainRegistry, QuantityT, UnitT +from .registry import ( + PlainRegistry, + GenericPlainRegistry, + QuantityT, + UnitT, + RegistryCache, +) from .quantity import MagnitudeT __all__ = [ @@ -27,6 +33,7 @@ "PlainUnit", "PlainQuantity", "PlainRegistry", + "RegistryCache", "AliasDefinition", "DefaultsDefinition", "DimensionDefinition", diff --git a/pint/facets/plain/registry.py b/pint/facets/plain/registry.py index fb7797d6c..e39c4b75c 100644 --- a/pint/facets/plain/registry.py +++ b/pint/facets/plain/registry.py @@ -9,7 +9,7 @@ - parse_unit_name: Parse a unit to identify prefix, unit name and suffix by walking the list of prefix and suffix. - Result is cached: NO + Result is cached: YES - parse_units: Parse a units expression and returns a UnitContainer with the canonical names. The expression can only contain products, ratios and powers of units; @@ -131,6 +131,11 @@ def __init__(self) -> None: #: Cache the unit name associated to user input. ('mV' -> 'millivolt') self.parse_unit: dict[str, UnitsContainer] = {} + #: Maps (string and case insensitive) to (prefix, unit name, suffix) + self.parse_unit_name: dict[ + tuple[str, bool], tuple[tuple[str, str, str], ...] + ] = {} + def __eq__(self, other: Any): if not isinstance(other, self.__class__): return False @@ -139,6 +144,7 @@ def __eq__(self, other: Any): "root_units", "dimensionality", "parse_unit", + "parse_unit_name", ) return all(getattr(self, attr) == getattr(other, attr) for attr in attrs) @@ -1040,8 +1046,14 @@ def _convert( return value + # @deprecated("Use parse_single_unit") def parse_unit_name( self, unit_name: str, case_sensitive: Optional[bool] = None + ) -> tuple[tuple[str, str, str], ...]: + return self.parse_single_unit(unit_name, case_sensitive) + + def parse_single_unit( + self, unit_name: str, case_sensitive: Optional[bool] = None ) -> tuple[tuple[str, str, str], ...]: """Parse a unit to identify prefix, unit name and suffix by walking the list of prefix and suffix. @@ -1061,17 +1073,33 @@ def parse_unit_name( tuple of tuples (str, str, str) all non-equivalent combinations of (prefix, unit name, suffix) """ - return self._dedup_candidates( - self._parse_unit_name(unit_name, case_sensitive=case_sensitive) - ) - def _parse_unit_name( - self, unit_name: str, case_sensitive: Optional[bool] = None - ) -> Generator[tuple[str, str, str], None, None]: - """Helper of parse_unit_name.""" case_sensitive = ( self.case_sensitive if case_sensitive is None else case_sensitive ) + + return self._parse_single_unit(unit_name, case_sensitive) + + def _parse_single_unit( + self, unit_name: str, case_sensitive: bool + ) -> tuple[tuple[str, str, str], ...]: + """Helper of parse_unit_name.""" + + key = (unit_name, case_sensitive) + this_cache = self._cache.parse_unit_name + if key in this_cache: + return this_cache[key] + + out = this_cache[key] = self._dedup_candidates( + self._yield_potential_units(unit_name, case_sensitive=case_sensitive) + ) + return out + + def _yield_potential_units( + self, unit_name: str, case_sensitive: bool + ) -> Generator[tuple[str, str, str], None, None]: + """Helper of parse_unit_name.""" + stw = unit_name.startswith edw = unit_name.endswith for suffix, prefix in itertools.product(self._suffixes, self._prefixes):