Skip to content

Commit

Permalink
dict[tuple[...], ...] type hints.
Browse files Browse the repository at this point in the history
This commit resolves a critical low-level issue in @beartype's dynamic
type-checking code generator for **nested tuple-in-dictionary type
hints** (i.e., type hints of the form `dict[tuple[...], ...]`),
resolving issue #371 kindly submitted by that wascally typing wabbit
@alisaifee (Ali-Akber Saifee). The accursed @beartype 0.18.X release
cycle continues to bedevil our poor world. (*Flattened madness is no glad flattery!*)
  • Loading branch information
leycec committed Apr 19, 2024
1 parent 8deda40 commit fd0096a
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 161 deletions.
242 changes: 122 additions & 120 deletions beartype/_check/code/codemagic.py
Expand Up @@ -31,123 +31,125 @@
exception messages.
'''

# ....................{ HINT ~ meta }....................
# Iterator yielding the next integer incrementation starting at 0, to be safely
# deleted *AFTER* defining the following 0-based indices via this iterator.
__hint_meta_index_counter = count(start=0, step=1)


HINT_META_INDEX_HINT = next(__hint_meta_index_counter)
'''
0-based index into each tuple of hint metadata providing the currently
visited hint.
For both space and time efficiency, this metadata is intentionally stored as
0-based integer indices of an unnamed tuple rather than:
* Human-readable fields of a named tuple, which incurs space and time costs we
would rather *not* pay.
* 0-based integer indices of a tiny fixed list. Previously, this metadata was
actually stored as a fixed list. However, exhaustive profiling demonstrated
that reinitializing each such list by slice-assigning that list's items from
a tuple to be faster than individually assigning these items:
.. code-block:: shell-session
$ echo 'Slice w/ tuple:' && command python3 -m timeit -s \
'muh_list = ["a", "b", "c", "d",]' \
'muh_list[:] = ("e", "f", "g", "h",)'
Slice w/ tuple:
2000000 loops, best of 5: 131 nsec per loop
$ echo 'Slice w/o tuple:' && command python3 -m timeit -s \
'muh_list = ["a", "b", "c", "d",]' \
'muh_list[:] = "e", "f", "g", "h"'
Slice w/o tuple:
2000000 loops, best of 5: 138 nsec per loop
$ echo 'Separate:' && command python3 -m timeit -s \
'muh_list = ["a", "b", "c", "d",]' \
'muh_list[0] = "e"
muh_list[1] = "f"
muh_list[2] = "g"
muh_list[3] = "h"'
Separate:
2000000 loops, best of 5: 199 nsec per loop
So, not only does there exist no performance benefit to flattened fixed lists,
there exists demonstrable performance costs.
'''


HINT_META_INDEX_PLACEHOLDER = next(__hint_meta_index_counter)
'''
0-based index into each tuple of hint metadata providing the **current
placeholder type-checking substring** (i.e., placeholder to be globally
replaced by a Python code snippet type-checking the current pith expression
against the hint described by this metadata on visiting that hint).
This substring provides indirection enabling the currently visited parent hint
to defer and delegate the generation of code type-checking each child argument
of that hint to the later time at which that child argument is visited.
Example
-------
For example, the
:func:`beartype._decor._hint._pep._pephint.make_func_pith_code` function might
generate intermediary code resembling the following on visiting the
:data:`Union` parent of a ``Union[int, str]`` object *before* visiting either
the :class:`int` or :class:`str` children of that object:
if not (
@{0}! or
@{1}!
):
raise get_func_pith_violation(
func=__beartype_func,
pith_name=$%PITH_ROOT_NAME/~,
pith_value=__beartype_pith_root,
)
Note the unique substrings ``"@{0}!"`` and ``"@{1}!"`` in that code, which that
function iteratively replaces with code type-checking each of the child
arguments of that :data:`Union` parent (i.e., :class:`int`, :class:`str`). The
final code memoized by that function might then resemble:
if not (
isinstance(__beartype_pith_root, int) or
isinstance(__beartype_pith_root, str)
):
raise get_func_pith_violation(
func=__beartype_func,
pith_name=$%PITH_ROOT_NAME/~,
pith_value=__beartype_pith_root,
)
'''


HINT_META_INDEX_PITH_EXPR = next(__hint_meta_index_counter)
'''
0-based index into each tuple of hint metadata providing the **current
pith expression** (i.e., Python code snippet evaluating to the current possibly
nested object of the passed parameter or return value to be type-checked
against the currently visited hint).
'''


HINT_META_INDEX_PITH_VAR_NAME = next(__hint_meta_index_counter)
'''
0-based index into each tuple of hint metadata providing the **current pith
variable name** (i.e., name of the unique local variable assigned the value of
the current pith either by a prior assignment statement or expression).
'''


HINT_META_INDEX_INDENT_LEVEL = next(__hint_meta_index_counter)
'''
0-based index into each tuple of hint metadata providing the **current
indentation level** (i.e., 1-based positive integer describing the current level
of indentation appropriate for the currently visited hint).
'''


# Delete the above counter for safety and sanity in equal measure.
del __hint_meta_index_counter
#FIXME: Preserved for documentation purposes. When time permits, centralized
#this documentation into the docstring of a new "HintMeta" dataclass, please.
# # ....................{ HINT ~ meta }....................
# # Iterator yielding the next integer incrementation starting at 0, to be safely
# # deleted *AFTER* defining the following 0-based indices via this iterator.
# __hint_meta_index_counter = count(start=0, step=1)
#
#
# HINT_META_INDEX_HINT = next(__hint_meta_index_counter)
# '''
# 0-based index into each tuple of hint metadata providing the currently
# visited hint.
#
# For both space and time efficiency, this metadata is intentionally stored as
# 0-based integer indices of an unnamed tuple rather than:
#
# * Human-readable fields of a named tuple, which incurs space and time costs we
# would rather *not* pay.
# * 0-based integer indices of a tiny fixed list. Previously, this metadata was
# actually stored as a fixed list. However, exhaustive profiling demonstrated
# that reinitializing each such list by slice-assigning that list's items from
# a tuple to be faster than individually assigning these items:
#
# .. code-block:: shell-session
#
# $ echo 'Slice w/ tuple:' && command python3 -m timeit -s \
# 'muh_list = ["a", "b", "c", "d",]' \
# 'muh_list[:] = ("e", "f", "g", "h",)'
# Slice w/ tuple:
# 2000000 loops, best of 5: 131 nsec per loop
# $ echo 'Slice w/o tuple:' && command python3 -m timeit -s \
# 'muh_list = ["a", "b", "c", "d",]' \
# 'muh_list[:] = "e", "f", "g", "h"'
# Slice w/o tuple:
# 2000000 loops, best of 5: 138 nsec per loop
# $ echo 'Separate:' && command python3 -m timeit -s \
# 'muh_list = ["a", "b", "c", "d",]' \
# 'muh_list[0] = "e"
# muh_list[1] = "f"
# muh_list[2] = "g"
# muh_list[3] = "h"'
# Separate:
# 2000000 loops, best of 5: 199 nsec per loop
#
# So, not only does there exist no performance benefit to flattened fixed lists,
# there exists demonstrable performance costs.
# '''
#
#
# HINT_META_INDEX_PLACEHOLDER = next(__hint_meta_index_counter)
# '''
# 0-based index into each tuple of hint metadata providing the **current
# placeholder type-checking substring** (i.e., placeholder to be globally
# replaced by a Python code snippet type-checking the current pith expression
# against the hint described by this metadata on visiting that hint).
#
# This substring provides indirection enabling the currently visited parent hint
# to defer and delegate the generation of code type-checking each child argument
# of that hint to the later time at which that child argument is visited.
#
# Example
# -------
# For example, the
# :func:`beartype._decor._hint._pep._pephint.make_func_pith_code` function might
# generate intermediary code resembling the following on visiting the
# :data:`Union` parent of a ``Union[int, str]`` object *before* visiting either
# the :class:`int` or :class:`str` children of that object:
#
# if not (
# @{0}! or
# @{1}!
# ):
# raise get_func_pith_violation(
# func=__beartype_func,
# pith_name=$%PITH_ROOT_NAME/~,
# pith_value=__beartype_pith_root,
# )
#
# Note the unique substrings ``"@{0}!"`` and ``"@{1}!"`` in that code, which that
# function iteratively replaces with code type-checking each of the child
# arguments of that :data:`Union` parent (i.e., :class:`int`, :class:`str`). The
# final code memoized by that function might then resemble:
#
# if not (
# isinstance(__beartype_pith_root, int) or
# isinstance(__beartype_pith_root, str)
# ):
# raise get_func_pith_violation(
# func=__beartype_func,
# pith_name=$%PITH_ROOT_NAME/~,
# pith_value=__beartype_pith_root,
# )
# '''
#
#
# HINT_META_INDEX_PITH_EXPR = next(__hint_meta_index_counter)
# '''
# 0-based index into each tuple of hint metadata providing the **current
# pith expression** (i.e., Python code snippet evaluating to the current possibly
# nested object of the passed parameter or return value to be type-checked
# against the currently visited hint).
# '''
#
#
# HINT_META_INDEX_PITH_VAR_NAME = next(__hint_meta_index_counter)
# '''
# 0-based index into each tuple of hint metadata providing the **current pith
# variable name** (i.e., name of the unique local variable assigned the value of
# the current pith either by a prior assignment statement or expression).
# '''
#
#
# HINT_META_INDEX_INDENT_LEVEL = next(__hint_meta_index_counter)
# '''
# 0-based index into each tuple of hint metadata providing the **current
# indentation level** (i.e., 1-based positive integer describing the current level
# of indentation appropriate for the currently visited hint).
# '''
#
#
# # Delete the above counter for safety and sanity in equal measure.
# del __hint_meta_index_counter

0 comments on commit fd0096a

Please sign in to comment.