You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A few of the public type hints exported by the numpy.typing subpackage are currently unusable at runtime, due to being either non-runtime-checkable protocols (i.e., PEP 544-compliant typing.Protocol subclasses not decorated by @typing.runtime_checkable) or type hints subscripted by one or more non-runtime-checkable protocols. Both prevent runtime-type checkers (like @beartype and typeguard) from supporting those hints.
This includes:
The core numpy.typing.ArrayLike type hint, which internally requires either the private non-runtime-checkable numpy.typing._array_like._SupportsArray or numpy._typing._nested_sequence._NestedSequence protocol (depending on NumPy version).
The core numpy.typing.DTypeLike type hints, which likewise internally required the private non-runtime-checkable numpy.typing._dtype_like._SupportsDType protocol under a prior NumPy version. I'm kinda unclear whether the current numpy.typing.DTypeLike implementation is similarly encumbered. It might be – or I might just be hitting an obscure edge case in @beartype. More on that below!
@beartype users are currently complaining about both of the above here and here. I'd like to mollify their distress. Also, I'd like to actually use these wonderful things myself. They're awesome!
All Good Things Begin with Arrays
numpy.typing.ArrayLike is unambiguously unusable at runtime, so that seems like a reasonable place to start. Admittedly, the following snippet requires the third-party @beartype runtime type-checker (which is not great). Still, that's probably the simplest way to exhibit this issue (which is great).
Consider this the runtime equivalent of a mypy error, which it kinda is:
Here, @beartype is telling us that numpy.typing.ArrayLike internally requires the private non-runtime-checkable numpy.typing._array_like._SupportsArray protocol. In theory, that can be trivially resolved by just decorating numpy.typing._array_like._SupportsArray with @typing.runtime_checkable: e.g.,
# In "numpy.typing._array_like":## Instead of just this...class_SupportsArray(Protocol[_DType_co]):
# ...do this!fromtypingimportruntime_checkable@runtime_checkable# <-- yes, this is nonsensical boilerplate.class_SupportsArray(Protocol[_DType_co]):
Yes, @typing.runtime_checkable is nonsensical boilerplate that has no adverse side effects whatsoever and absolutely should have just been applied unconditionally for all protocols. But PEP 544 authors disliked runtime at the time for "reasons" and now we're stuck with it. What you gonna do?
Ideally, similar boilerplate should be applied to all protocols declared throughout the numpy.typing subpackage. I feel your annoyance and raise my fist in solidarity.
Like, It's DTypeLike
numpy.typing.DTypeLike used to be unusable at runtime for similar reasons. But the underlying implementation appears to have significantly changed. I'm unclear exactly what the remaining issue is, but suspect this might be on @beartype's end: e.g.,
On the one hand, @beartype is fully compliant with PEP 484- and 585-style generics (both subscripted and unsubscripted) as well PEP 544-style protocols (both subscripted and unsubscripted). It's been a few months since we've had an issue submitted against either.
On the other hand, @beartype appears to be implying above that numpy.typing.DTypeLike reduces to numpy.dtype[Any]and that the metaclass of numpy.dtype[Any] defines __isinstancecheck__() to prohibit runtime checks. The exception message "isinstance() argument 2 cannot be a parameterized generic" sounds suspiciously like those raised by the standard PEP 585 superclass types.GenericAlias. If so, this is probably on @beartype – which now needs to additionally support numpy.dtype as a new PEP 585-like thing.
Is that right? If so, would it be sensible for @beartype to just quietly ignore the child type hint subscripting numpy.dtype[...] for the moment?
That's totally fine, of course. We'll happily do all that. Generics and protocols are a wicked darkness that ruthlessly squirm out of your test suite's grasp with every commit. It'd be great to nail that darkness down to the floor for a bit.
Glory Be to NumPy
I debated whether this was a bug or a feature request. I erred on the side of bug, as the unusability of NumPy functionality at runtime that could be trivially usable smells of buggishness. Please relabel this as feature request if I erred on the wrong side.
Thanks so much for all the tremendous volunteerism, breathtaking NumPy devs! You make the burgeoning Big Data world go round. 🥰
Traceback (most recent call last):
File "/home/leycec/py/beartype/beartype/_util/cls/pep/utilpep3119.py", line 124, in die_unless_type_isinstanceable
isinstance(None, cls) # type: ignore[arg-type]
File "/usr/lib/python3.10/typing.py", line 1498, in __instancecheck__
raise TypeError("Instance and class checks can only be used with"
TypeError: Instance and class checks can only be used with @runtime_checkable protocols
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/leycec/tmp/mopy.py", line 7, in<module>
def data_science_or_bust(array: ArrayLike) -> int:
File "/home/leycec/py/beartype/beartype/_decor/_cache/cachedecor.py", line 77, in beartype
return beartype_object(obj, conf)
File "/home/leycec/py/beartype/beartype/_decor/decorcore.py", line 239, in beartype_object
return _beartype_func( # type: ignore[return-value]
File "/home/leycec/py/beartype/beartype/_decor/decorcore.py", line 579, in _beartype_func
func_wrapper_code = generate_code(bear_call)
File "/home/leycec/py/beartype/beartype/_decor/_wrapper/wrappermain.py", line 215, in generate_code
code_check_params = _code_check_args(bear_call)
File "/home/leycec/py/beartype/beartype/_decor/_wrapper/wrappermain.py", line 475, in _code_check_args
reraise_exception_placeholder(
File "/home/leycec/py/beartype/beartype/_util/error/utilerror.py", line 212, in reraise_exception_placeholder
raise exception.with_traceback(exception.__traceback__)
File "/home/leycec/py/beartype/beartype/_decor/_wrapper/wrappermain.py", line 450, in _code_check_args
) = make_func_wrapper_code(hint)
File "/home/leycec/py/beartype/beartype/_util/cache/utilcachecall.py", line 339, in _callable_cached
raise exception
File "/home/leycec/py/beartype/beartype/_util/cache/utilcachecall.py", line 331, in _callable_cached
return_value = params_flat_to_return_value[params_flat] = func(
File "/home/leycec/py/beartype/beartype/_decor/_wrapper/_wrappercode.py", line 71, in make_func_wrapper_code
) = make_check_expr(hint)
File "/home/leycec/py/beartype/beartype/_util/cache/utilcachecall.py", line 339, in _callable_cached
raise exception
File "/home/leycec/py/beartype/beartype/_util/cache/utilcachecall.py", line 331, in _callable_cached
return_value = params_flat_to_return_value[params_flat] = func(
File "/home/leycec/py/beartype/beartype/_check/expr/exprmake.py", line 1712, in make_check_expr
hint_curr_expr=add_func_scope_type(
File "/home/leycec/py/beartype/beartype/_check/expr/_exprscope.py", line 196, in add_func_scope_type
die_unless_type_isinstanceable(cls=cls, exception_prefix=exception_prefix)
File "/home/leycec/py/beartype/beartype/_util/cls/pep/utilpep3119.py", line 165, in die_unless_type_isinstanceable
raise exception_cls(exception_message) from exception
beartype.roar.BeartypeDecorHintPep3119Exception: @beartyped __main__.data_science_or_bust()
parameter "array"type hint <class 'numpy.typing._array_like._SupportsArray'> uncheckable at runtime
(i.e., not passable as second parameter to isinstance(), due to raising "Instance and class checks canonly be used with @runtime_checkable protocols" from metaclass __instancecheck__() method).
BvB93
changed the title
BUG: A subset of "numpy.typing" type hints remain unusable at runtime
ENH: A subset of "numpy.typing" type hints remain unusable at runtime
Sep 30, 2022
Thanks for the suggestion @leycec. I'll be honest: the lack of runtime checkable protocols in the numpy codebase is more of an oversight than anything else, and I agree that this would be a useful feature to have for runtime type checkers.
Describe the issue:
A few of the public type hints exported by the
numpy.typing
subpackage are currently unusable at runtime, due to being either non-runtime-checkable protocols (i.e., PEP 544-complianttyping.Protocol
subclasses not decorated by@typing.runtime_checkable
) or type hints subscripted by one or more non-runtime-checkable protocols. Both prevent runtime-type checkers (like @beartype andtypeguard
) from supporting those hints.This includes:
numpy.typing.ArrayLike
type hint, which internally requires either the private non-runtime-checkablenumpy.typing._array_like._SupportsArray
ornumpy._typing._nested_sequence._NestedSequence
protocol (depending on NumPy version).numpy.typing.DTypeLike
type hints, which likewise internally required the private non-runtime-checkablenumpy.typing._dtype_like._SupportsDType
protocol under a prior NumPy version. I'm kinda unclear whether the currentnumpy.typing.DTypeLike
implementation is similarly encumbered. It might be – or I might just be hitting an obscure edge case in @beartype. More on that below!The one notable exception is
numpy.typing.NDArray[...]
, which thankfully is usable at runtime. Thanks to this, @beartype has explicitly supportednumpy.typing.NDArray[...]
for over a year. The @beartype userbase, which is mostly data scientists and machine learning gurus, is grateful. This is probably a useful moment to admit that I maintain @beartype. 😅@beartype users are currently complaining about both of the above here and here. I'd like to mollify their distress. Also, I'd like to actually use these wonderful things myself. They're awesome!
All Good Things Begin with Arrays
numpy.typing.ArrayLike
is unambiguously unusable at runtime, so that seems like a reasonable place to start. Admittedly, the following snippet requires the third-party @beartype runtime type-checker (which is not great). Still, that's probably the simplest way to exhibit this issue (which is great).Consider this the runtime equivalent of a mypy error, which it kinda is:
Here, @beartype is telling us that
numpy.typing.ArrayLike
internally requires the private non-runtime-checkablenumpy.typing._array_like._SupportsArray
protocol. In theory, that can be trivially resolved by just decoratingnumpy.typing._array_like._SupportsArray
with@typing.runtime_checkable
: e.g.,Yes,
@typing.runtime_checkable
is nonsensical boilerplate that has no adverse side effects whatsoever and absolutely should have just been applied unconditionally for all protocols. But PEP 544 authors disliked runtime at the time for "reasons" and now we're stuck with it. What you gonna do?Ideally, similar boilerplate should be applied to all protocols declared throughout the
numpy.typing
subpackage. I feel your annoyance and raise my fist in solidarity.Like, It's DTypeLike
numpy.typing.DTypeLike
used to be unusable at runtime for similar reasons. But the underlying implementation appears to have significantly changed. I'm unclear exactly what the remaining issue is, but suspect this might be on @beartype's end: e.g.,That is pure unadulterated chaos.
On the one hand, @beartype is fully compliant with PEP 484- and 585-style generics (both subscripted and unsubscripted) as well PEP 544-style protocols (both subscripted and unsubscripted). It's been a few months since we've had an issue submitted against either.
On the other hand, @beartype appears to be implying above that
numpy.typing.DTypeLike
reduces tonumpy.dtype[Any]
and that the metaclass ofnumpy.dtype[Any]
defines__isinstancecheck__()
to prohibit runtime checks. The exception message"isinstance() argument 2 cannot be a parameterized generic"
sounds suspiciously like those raised by the standard PEP 585 superclasstypes.GenericAlias
. If so, this is probably on @beartype – which now needs to additionally supportnumpy.dtype
as a new PEP 585-like thing.Is that right? If so, would it be sensible for @beartype to just quietly ignore the child type hint subscripting
numpy.dtype[...]
for the moment?That's totally fine, of course. We'll happily do all that. Generics and protocols are a wicked darkness that ruthlessly squirm out of your test suite's grasp with every commit. It'd be great to nail that darkness down to the floor for a bit.
Glory Be to NumPy
I debated whether this was a bug or a feature request. I erred on the side of bug, as the unusability of NumPy functionality at runtime that could be trivially usable smells of buggishness. Please relabel this as feature request if I erred on the wrong side.
Thanks so much for all the tremendous volunteerism, breathtaking NumPy devs! You make the burgeoning Big Data world go round. 🥰
Reproduce the code example:
Error message:
NumPy/Python version information:
1.22.4 3.10.6 (main, Sep 6 2022, 17:16:18) [GCC 11.3.0]
Context for the issue:
This issue prevents various NumPy type hints from being used at runtime – especially by runtime type-checkers like @beartype and
typeguard
. Gah!The text was updated successfully, but these errors were encountered: