Skip to content

Commit

Permalink
Indentation code generation x 1.
Browse files Browse the repository at this point in the history
This commit is the first in a commit chain optimizing the dynamic code
generation performed by @beartype to avoid unnecessary string formatting
with respect to code indentation. Specifically, this commit defines and
exhaustively tests a new `beartype._data.code.datacodeindent` submodule
caching frequently used code indentation substrings with a `dict`
subclass overriding the `__missing__()` dunder method. Unrelatedly, this
commit also significantly optimizes all other `dict` subclasses
overriding the `__missing__()` dunder method littered throughout the
codebase to cache the values returned by those methods.
(*Frequent eloquence minces no friendly dancers!*)
  • Loading branch information
leycec committed Feb 17, 2024
1 parent 9ee9c82 commit f321589
Show file tree
Hide file tree
Showing 19 changed files with 317 additions and 175 deletions.
124 changes: 51 additions & 73 deletions beartype/_cave/_cavemap.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,24 @@
BeartypeCaveNoneTypeOrKeyException,
BeartypeCaveNoneTypeOrMutabilityException,
)
from beartype._util.hint.nonpep.utilnonpeptest import (
die_unless_hint_nonpep)
from typing import Any, Tuple, Union
from beartype.typing import (
Any,
Tuple,
Union,
)
from beartype._util.hint.nonpep.utilnonpeptest import die_unless_hint_nonpep

# ....................{ CONSTANTS }....................
_NoneType: type = type(None)
'''
Type of the ``None`` singleton, duplicated from the :mod:`beartype.cave`
Type of the :data:`None` singleton, duplicated from the :mod:`beartype.cave`
submodule to prevent cyclic import dependencies.
'''


_NoneTypes: Tuple[type, ...] = (_NoneType,)
'''
Tuple of only the type of the ``None`` singleton.
Tuple of only the type of the :data:`None` singleton.
'''

# ....................{ HINTS }....................
Expand All @@ -61,36 +64,10 @@ class _NoneTypeOrType(dict):
:class:`NoneType` **tuple factory type** (i.e., :class:`dict` subclass,
instances of which are dictionaries mapping from arbitrary types or tuples
of types to the same types or tuples of types concatenated with the type of
the ``None`` singleton).
the :data:`None` singleton).
'''

# ..................{ DUNDERS }..................
def __setitem__(self, key: Any, value: Any) -> None:
'''
Dunder method explicitly called by the superclass on setting the passed
key-value pair with``[``- and ``]``-delimited syntax.
Specifically, this method prohibits external attempts to explicitly set
key-value pairs on this factory by unconditionally raising an
exception.
Parameters
----------
key : object
Key to map this value to.
value : object
Value to be mapped to.
Raises
----------
BeartypeCaveNoneTypeOrMutabilityException
Unconditionally.
'''

raise BeartypeCaveNoneTypeOrMutabilityException(
f'{repr(self)} externally immutable (i.e., not settable).')


def __missing__(self, hint: Union[type, str, _TypeTuple]) -> _TypeTuple:
'''
Dunder method explicitly called by the superclass
Expand All @@ -102,33 +79,33 @@ def __missing__(self, hint: Union[type, str, _TypeTuple]) -> _TypeTuple:
* If a single type or string is passed:
#. Creates a new 2-tuple containing only that object and the type of
the ``None`` singleton.
the :data:`None` singleton.
#. Maps the passed type to that 2-tuple.
#. Returns that 2-tuple.
* Else if a tuple of one or more types and/or strings is passed:
#. Creates a new tuple appending the type of the ``None`` singleton
to the passed tuple.
#. Creates a new tuple appending the type of the :data:`None`
singleton to the passed tuple.
#. Maps the passed type to the new tuple.
#. Returns the new tuple.
* Else, raises an exception.
Parameters
----------
hint : (type, str, _TypeTuple)
hint : Union[type, str, _TypeTuple]
Type, string, or tuple of one or more types and/or strings *not*
currently cached by this factory.
Returns
----------
-------
_TypeTuple
Tuple of types appending the type of the ``None`` singleton to the
passed type, string, or tuple of types and/or strings.
Tuple of types appending the type of the :data:`None` singleton to
the passed type, string, or tuple of types and/or strings.
Raises
----------
------
BeartypeCaveNoneTypeOrKeyException
If this key is neither:
Expand Down Expand Up @@ -177,52 +154,51 @@ def __missing__(self, hint: Union[type, str, _TypeTuple]) -> _TypeTuple:
raise BeartypeCaveNoneTypeOrKeyException(
f'"NoneTypeOr" key {repr(hint)} unsupported.')

# Return this new tuple.
#
# The superclass dict.__getitem__() dunder method then implicitly maps
# the passed missing key to this new tuple of types by effectively:
# self[hint] = hint_or_none
# Cache this tuple under this key.
self[hint] = hint_or_none

# Return this tuple.
return hint_or_none

# ....................{ SINGLETONS }....................
NoneTypeOr: Any = _NoneTypeOrType()
'''
**:class:``NoneType`` tuple factory** (i.e., dictionary mapping from arbitrary
types or tuples of types to the same types or tuples of types concatenated with
the type of the ``None`` singleton).
the type of the :data:`None` singleton).
This factory efficiently generates and caches tuples of types containing
:class:``NoneType`` from arbitrary user-specified types and tuples of types. To
:class:`NoneType` from arbitrary user-specified types and tuples of types. To
do so, simply index this factory with any desired type *or* tuple of types; the
corresponding value will then be another tuple containing :class:``NoneType``
corresponding value will then be another tuple containing :class:`NoneType`
and that type *or* those types.
Motivation
----------
This factory is commonly used to type-hint **optional callable parameters**
(i.e., parameters defaulting to ``None`` when *not* explicitly passed by the
(i.e., parameters defaulting to :data:`None` when *not* explicitly passed by the
caller). Although such parameters may also be type-hinted with a tuple manually
containing :class:``NoneType``, doing so inefficiently recreates these tuples
containing :class:`NoneType`, doing so inefficiently recreates these tuples
for each optional callable parameter across the entire codebase.
This factory avoids such inefficient recreation. Instead, when indexed with any
arbitrary key:
* If that key has already been successfully accessed on this factory, this
factory returns the existing value (i.e., tuple containing
:class:``NoneType`` and that key if that key is a type *or* the items of that
key if that key is a tuple) previously mapped and cached to that key.
factory returns the existing value (i.e., tuple containing :class:`NoneType`
and that key if that key is a type *or* the items of that key if that key is a
tuple) previously mapped and cached to that key.
* Else, if that key is:
* A type, this factory:
#. Creates a new tuple containing that type and :class:``NoneType``.
#. Creates a new tuple containing that type and :class:`NoneType`.
#. Associates that key with that tuple.
#. Returns that tuple.
* A tuple of types, this factory:
#. Creates a new tuple containing these types and :class:``NoneType``.
#. Creates a new tuple containing these types and :class:`NoneType`.
#. Associates that key with that tuple.
#. Returns that tuple.
Expand All @@ -233,22 +209,24 @@ def __missing__(self, hint: Union[type, str, _TypeTuple]) -> _TypeTuple:
type despite otherwise *not* complying with :pep:`484`_.
Examples
----------
# Function accepting an optional parameter with neither
# "beartype.cave" nor "typing".
>>> def to_autumn(season_of_mists: (str, type(None)) = None) -> str
... return season_of_mists if season_of_mists is not None else (
... 'While barred clouds bloom the soft-dying day,')
# Function accepting an optional parameter with "beartype.cave".
>>> from beartype.cave import NoneTypeOr
>>> def to_autumn(season_of_mists: NoneTypeOr[str] = None) -> str
... return season_of_mists if season_of_mists is not None else (
... 'Then in a wailful choir the small gnats mourn')
# Function accepting an optional parameter with "typing".
>>> from typing import Optional
>>> def to_autumn(season_of_mists: Optional[str] = None) -> str
... return season_of_mists if season_of_mists is not None else (
... 'Or sinking as the light wind lives or dies;')
--------
.. code-block:: pycon
# Function accepting an optional parameter with neither
# "beartype.cave" nor "typing".
>>> def to_autumn(season_of_mists: (str, type(None)) = None) -> str
... return season_of_mists if season_of_mists is not None else (
... 'While barred clouds bloom the soft-dying day,')
# Function accepting an optional parameter with "beartype.cave".
>>> from beartype.cave import NoneTypeOr
>>> def to_autumn(season_of_mists: NoneTypeOr[str] = None) -> str
... return season_of_mists if season_of_mists is not None else (
... 'Then in a wailful choir the small gnats mourn')
# Function accepting an optional parameter with "typing".
>>> from typing import Optional
>>> def to_autumn(season_of_mists: Optional[str] = None) -> str
... return season_of_mists if season_of_mists is not None else (
... 'Or sinking as the light wind lives or dies;')
'''
46 changes: 21 additions & 25 deletions beartype/_check/code/codemake.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,23 +72,21 @@
PEP593_CODE_HINT_VALIDATOR_PREFIX,
PEP593_CODE_HINT_VALIDATOR_SUFFIX,
)
from beartype._check.convert.convsanify import sanify_hint_any
from beartype._conf.confcls import BeartypeConf
from beartype._data.code.datacodeindent import (
CODE_INDENT_1,
CODE_INDENT_2,
)
from beartype._data.code.datacodemagic import (
LINE_RSTRIP_INDEX_AND,
LINE_RSTRIP_INDEX_OR,
)
from beartype._data.hint.datahinttyping import (
CodeGenerated,
LexicalScope,
TypeStack,
)
from beartype._util.cache.utilcachecall import callable_cached
from beartype._util.cache.pool.utilcachepoollistfixed import (
FIXED_LIST_SIZE_MEDIUM,
acquire_fixed_list,
release_fixed_list,
)
from beartype._util.cache.pool.utilcachepoolobjecttyped import (
acquire_object_typed,
release_object_typed,
)
from beartype._data.hint.datahinttyping import LexicalScope
from beartype._util.error.utilerrorraise import EXCEPTION_PLACEHOLDER
from beartype._data.hint.pep.sign.datapepsigns import (
HintSignAnnotated,
HintSignForwardRef,
Expand All @@ -105,6 +103,17 @@
HINT_SIGNS_ORIGIN_ISINSTANCEABLE,
HINT_SIGNS_UNION,
)
from beartype._util.cache.utilcachecall import callable_cached
from beartype._util.cache.pool.utilcachepoollistfixed import (
FIXED_LIST_SIZE_MEDIUM,
acquire_fixed_list,
release_fixed_list,
)
from beartype._util.cache.pool.utilcachepoolobjecttyped import (
acquire_object_typed,
release_object_typed,
)
from beartype._util.error.utilerrorraise import EXCEPTION_PLACEHOLDER
from beartype._util.func.utilfuncscope import add_func_scope_attr
from beartype._util.hint.pep.proposal.pep484585.utilpep484585 import (
is_hint_pep484585_tuple_empty)
Expand Down Expand Up @@ -136,27 +145,14 @@
is_hint_pep_args,
warn_if_hint_pep_deprecated,
)
from beartype._check.convert.convsanify import sanify_hint_any
from beartype._util.hint.utilhinttest import is_hint_ignorable
from beartype._util.kind.map.utilmapset import update_mapping
from beartype._util.text.utiltextmagic import (
CODE_INDENT_1,
CODE_INDENT_2,
LINE_RSTRIP_INDEX_AND,
LINE_RSTRIP_INDEX_OR,
)
from beartype._util.text.utiltextmunge import replace_str_substrs
from beartype._util.text.utiltextrepr import represent_object
from collections.abc import Callable
from random import getrandbits

# ....................{ MAKERS }....................
#FIXME: Attempt to JIT this function with Numba at some point. This will almost
#certainly either immediately blow up or improve nothing, but we're curious to
#see what happens. Make it so, Ensign Numba!
# from numba import jit
# @jit

@callable_cached
def make_check_expr(
# ..................{ ARGS ~ mandatory }..................
Expand Down
16 changes: 16 additions & 0 deletions beartype/_check/code/snip/codesnipcls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env python3
# --------------------( LICENSE )--------------------
# Copyright (c) 2014-2024 Beartype authors.
# See "LICENSE" for further details.

'''
Beartype **type-checking expression snippet classes** (i.e., low-level classes
dynamically and efficiently generating substrings intended to be interpolated
into boolean expressions type-checking arbitrary objects against various type
hints).
This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS }....................

7 changes: 3 additions & 4 deletions beartype/_check/code/snip/codesnipstr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
# See "LICENSE" for further details.

'''
Beartype decorator **type-checking expression snippets** (i.e., triple-quoted
pure-Python string constants formatted and concatenated together to dynamically
generate boolean expressions type-checking arbitrary objects against arbitrary
PEP-compliant type hints).
Beartype **type-checking expression snippets** (i.e., triple-quoted pure-Python
string constants formatted and concatenated together to dynamically generate
boolean expressions type-checking arbitrary objects against various type hints).
This private submodule is *not* intended for importation by downstream callers.
'''
Expand Down
2 changes: 1 addition & 1 deletion beartype/_check/error/_pep/_errorpep593.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
get_hint_pep593_metadata,
get_hint_pep593_metahint,
)
from beartype._util.text.utiltextmagic import CODE_INDENT_1
from beartype._data.code.datacodeindent import CODE_INDENT_1

# ....................{ GETTERS }....................
def find_cause_annotated(cause: ViolationCause) -> ViolationCause:
Expand Down
15 changes: 7 additions & 8 deletions beartype/_check/forward/fwdscope.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from beartype._check.forward.reference.fwdrefmake import (
make_forwardref_indexable_subtype)
from beartype._util.text.utiltextidentifier import die_unless_identifier
# from sys import modules as sys_modules

# ....................{ SUBCLASSES }....................
#FIXME: Unit test us up, please.
Expand Down Expand Up @@ -108,7 +107,7 @@ def __init__(self, scope_dict: LexicalScope, scope_name: str) -> None:
to resolve a nested class or callable against this scope).
Raises
----------
------
BeartypeDecorHintForwardRefException
If this scope name is *not* a valid Python attribute name.
'''
Expand Down Expand Up @@ -162,13 +161,13 @@ def __missing__(self, hint_name: str) -> Type[
name of this unresolved type hint.
Returns
----------
-------
Type[_BeartypeForwardRefIndexableABC]
Forward reference proxy deferring the resolution of this unresolved
type hint.
Raises
----------
------
BeartypeDecorHintForwardRefException
If this type hint name is *not* a valid Python attribute name.
'''
Expand All @@ -186,8 +185,8 @@ def __missing__(self, hint_name: str) -> Type[
forwardref_subtype = make_forwardref_indexable_subtype(
self._scope_name, hint_name)

# Return this proxy. The superclass dict.__getitem__() dunder method
# then implicitly maps the passed unresolved type hint name to this
# proxy by effectively assigning this name to this proxy: e.g.,
# self[hint_name] = forwardref_subtype
# Cache this proxy.
self[hint_name] = forwardref_subtype

# Return this proxy.
return forwardref_subtype
2 changes: 1 addition & 1 deletion beartype/_check/util/_checkutilsnip.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
ARG_NAME_GETRANDBITS,
VAR_NAME_RANDOM_INT,
)
from beartype._util.text.utiltextmagic import CODE_INDENT_1
from beartype._data.code.datacodeindent import CODE_INDENT_1

# ....................{ CODE }....................
CODE_SIGNATURE_ARG = (
Expand Down
Empty file added beartype/_data/code/__init__.py
Empty file.

0 comments on commit f321589

Please sign in to comment.