[Bug] Type-checking violations for lambda-style functional validators defined in IPython REPL omit source code
See original GitHub issueI really like the idea of functional validators. They make the source code much more robust while keeping it lean and clean. My only concern is that when the bear roars, the user has no idea what she/he did wrong. Probably, I am using beartype the wrong way.
Using a simple type the user gets an kind of helpful warning, e.g.,
>>> import numpy as np
>>> from beartype import beartype
>>> from beartype.vale import Is
>>> from numpy.typing import NDArray
>>> from typing_extensions import Annotated
>>> @beartype
... def func(a: int) -> int:
... return a
...
>>>
>>> func(5)
5
>>> func(5.)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<@beartype(__main__.func) at 0x7f6f2cb03940>", line 21, in func
File "/home/user/anaconda3/envs/py3.8/lib/python3.8/site-packages/beartype/_decor/_error/errormain.py", line 301, in raise_pep_call_exception
raise exception_cls( # type: ignore[misc]
beartype.roar.BeartypeCallHintParamViolation: @beartyped func() parameter a=5.0 violates type hint <class 'int'>, as 5.0 not instance of int.
so it is clear, that an integer was expected. In this case it is trivial, but probably it would be helpful to also print the type of the input variable. So that it becomes clearer. Using a validator these error are getting very cryptic. Lets define three super handy validators.
>>> Float2DArray = Annotated[
... NDArray[np.floating],
... Is[lambda arr: arr.ndim == 2],
... ]
>>> FloatMatrix = Annotated[
... Float2DArray,
... Is[lambda arr: arr.shape[0] == arr.shape[1]],
... ]
>>> SymmetricFloatMatrix = Annotated[
... FloatMatrix,
... Is[lambda arr: np.allclose(arr, arr.T)],
... ]
>>>
>>> @beartype
... def func(arr: SymmetricFloatMatrix) -> SymmetricFloatMatrix:
... return arr
...
>>>
>>> # this runs fine
>>> mat = np.zeros((4, 4))
>>> func(mat)
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
So far, I am happy. But now parsing an array of wrong shape,
>>> # not 2D
>>> mat = np.zeros((4, ))
>>> func(mat)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<@beartype(__main__.func) at 0x7f6f2f37e820>", line 53, in func
File "/home/user/anaconda3/envs/py3.8/lib/python3.8/site-packages/beartype/_decor/_error/errormain.py", line 301, in raise_pep_call_exception
raise exception_cls( # type: ignore[misc]
beartype.roar.BeartypeCallHintParamViolation: @beartyped func() parameter arr="array([0., 0., 0., 0.])" violates type hint typing_extensions.Annotated[numpy.ndarray, Is[<lambda>], Is[<lambda>], Is[<lambda>]], as "array([0., 0., 0., 0.])" violates validator Is[<lambda>]:
False == Is[<lambda>].
a non quadratic array (matrix), or
>>> # not quadratic
>>> mat = np.zeros((4, 5))
>>> func(mat)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<@beartype(__main__.func) at 0x7f6f2f37e820>", line 53, in func
File "/home/user/anaconda3/envs/py3.8/lib/python3.8/site-packages/beartype/_decor/_error/errormain.py", line 301, in raise_pep_call_exception
raise exception_cls( # type: ignore[misc]
beartype.roar.BeartypeCallHintParamViolation: @beartyped func() parameter arr="array([[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
...[ violates type hint typing_extensions.Annotated[numpy.ndarray, Is[<lambda>], Is[<lambda>], Is[<lambda>]], as "array([[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
...[ violates validator Is[<lambda>]:
False == Is[<lambda>].
a non symmetric matrix,
>>> # not symmetric
>>> mat = np.zeros((4, 4))
>>> mat[0, 1] = 2
>>> func(mat)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<@beartype(__main__.func) at 0x7f6f2f37e820>", line 53, in func
File "/home/user/anaconda3/envs/py3.8/lib/python3.8/site-packages/beartype/_decor/_error/errormain.py", line 301, in raise_pep_call_exception
raise exception_cls( # type: ignore[misc]
beartype.roar.BeartypeCallHintParamViolation: @beartyped func() parameter arr="array([[0., 2., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0... violates type hint typing_extensions.Annotated[numpy.ndarray, Is[<lambda>], Is[<lambda>], Is[<lambda>]], as "array([[0., 2., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0... violates validator Is[<lambda>]:
False == Is[<lambda>].
In each case I am getting errors that some Is[<lambda>]
are not fulfiled. If I remember correctly, older versions of beartype did print the lambda functions code. Using a wrong type, the error is more helpful, e.g.,
>>> func(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<@beartype(__main__.func) at 0x7f6f2f37e820>", line 53, in func
File "/home/user/anaconda3/envs/py3.8/lib/python3.8/site-packages/beartype/_decor/_error/errormain.py", line 301, in raise_pep_call_exception
raise exception_cls( # type: ignore[misc]
beartype.roar.BeartypeCallHintParamViolation: @beartyped func() parameter arr=5 violates type hint typing_extensions.Annotated[numpy.ndarray, Is[<lambda>], Is[<lambda>], Is[<lambda>]], as 5 not instance of <protocol "numpy.ndarray">.
I would love to see a list with requirements that the variable needs to fulfill. So something like:
beartype.roar.BeartypeCallHintParamViolation: @beartyped func() parameter arr="array([[0., 2., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0..
violates type hint SymmetricFloatMatrix:
- numpy.typing.NDArray[numpy.floating]
- lambda arr: arr.ndim == 2
- lambda arr: arr.shape[0] == arr.shape[1]
- lambda arr: np.allclose(arr, arr.T)
So back to the original question. Is is possible to improve readability of the errors?
Issue Analytics
- State:
- Created a year ago
- Comments:13 (7 by maintainers)
Top GitHub Comments
My $0.02 here: out of habit, I have my all validators defined as functions, e.g.
so failures can be immediately recognized by the function name.
Note that
beartype.vale
is pretty powerful, and some of the things you’d want to check can be implemented using native primitives. For example,works, or if you want to get fancy:
would all work too. HTH!
Because… it is hot.
Seriously, though. Good catch – as always! KISS or GTFO. But what is this is shadow madness:
furious Googling intensifies
EDIT: zomg. I am lounging in stunned disbelief.
@object.__new__
is actually a well-known crazy idiom for implicitly instantiating an object of the decorated type with the same name as that type, which then shadows that type in that scope, which thepyright
static type-checker insanely recognizes as valid at static analysis time: e.g.,Both of these exude the suspicious odour of “wtf, bro” – but
@object.__new__
really takes the cake and then splatters it all over the vinyl upholstery while recapitulating Bon Jovi’s trash-tier “It’s my life!”.Relatedly, if we’re maximizing terseness for all the lols:
💪