-
-
Notifications
You must be signed in to change notification settings - Fork 11.2k
ENH: Allow subscript access for np.bool
by adding __class_getitem__
#29370
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
ENH: Allow subscript access for np.bool
by adding __class_getitem__
#29370
Conversation
cls[Any] | ||
if cls == np.bool: | ||
# np.bool allows subscript | ||
assert cls[Any] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this one has been sitting for a month or so, perhaps because it is a bit esoteric. I checked locally that the regression test fails before the source patch is applied.
I also verified that assert_type(cls, np.dtype[npt.NDArray[bool]])
works on this branch, as does the original assert_type(cls, np.dtype[np.bool[bool]])
. I find the latter a little harder to parse, perhaps because it appears to be information-redundant, but I mostly gave up on understanding the static typing details beyond the surface level.
Anyway, I guess this one is for @jorenham to check? Would it make sense to have the explicit assert_type
check somewhere as well, rather than just the absence of error here? I'm pretty sure I've seen some test files with assert_type
all over the place--not sure if those would run the runtime checks or just the "type checking" time checks, but Joren will know.
I'm not sure I really want to know the answer, but is there an intuitive explanation for why only the np.bool
type allows subscripting, and not the others?
The runtime type check seems a bit confusing though, because the diff below the fold also appears to pass on this branch:
--- a/numpy/_core/tests/test_scalar_methods.py
+++ b/numpy/_core/tests/test_scalar_methods.py
@@ -4,7 +4,7 @@
import fractions
import platform
import types
-from typing import Any
+from typing import Any, assert_type
import pytest
@@ -171,12 +171,7 @@ def test_abc_non_numeric(self, cls: type[np.generic]) -> None:
@pytest.mark.parametrize("code", np.typecodes["All"])
def test_concrete(self, code: str) -> None:
cls = np.dtype(code).type
- if cls == np.bool:
- # np.bool allows subscript
- assert cls[Any]
- else:
- with pytest.raises(TypeError):
- cls[Any]
+ assert_type(cls, np.dtype[np.bool[bool]])
The reveal_type
seems pretty crude for cls
in general across the types at runtime here, which kind of makes me wonder how much value one really gets out of the runtime check in the original ticket--I suppose not much, and the only real argument was that it shouldn't error out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for checking this so thoroughly!
Would it make sense to have the explicit assert_type check somewhere as well, rather than just the absence of error here?
I agree it could be useful to add an explicit assert_type
test.
I am happy to update the PR to include that. But, I am also not sure about the right place to add them, so I will wait an answer from Joren.
is there an intuitive explanation for why only the np.bool type allows subscripting, and not the others?
If my understanding is correct, some users perfer to treat True and False as independent types similar to Literal[True]
and Literal[False]
. (Boolean has only two variants, so it is possible to do so.)
def sample(must_positive_flags: np.bool[True]):
....
The reveal_type seems pretty crude for cls in general across the types at runtime here, which kind of makes me wonder how much value one really gets out of the runtime check in the original ticket--I suppose not much, and the only real argument was that it shouldn't error out.
Thank you again for the further suggestions. I didn't realize that reveal_type
is so crude for cls.
I might have over-modified things here, as you point out. I'll fix it if you think it’s preferable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also verified that
assert_type(cls, np.dtype[npt.NDArray[bool]])
works on this branch, as does the originalassert_type(cls, np.dtype[np.bool[bool]])
. I find the latter a little harder to parse, perhaps because it appears to be information-redundant, but I mostly gave up on understanding the static typing details beyond the surface level.
You could also write assert_type(cls, np.dtype[np.bool])
, because np.bool
is exactly equivalent to np.bool[bool]
.
Would it make sense to have the explicit
assert_type
check somewhere as well, rather than just the absence of error here? I'm pretty sure I've seen some test files withassert_type
all over the place--not sure if those would run the runtime checks or just the "type checking" time checks, but Joren will know.
assert_type
is purely a static check, and this PR only affects runtime behavior, so I don't think that's needed.
I'm not sure I really want to know the answer, but is there an intuitive explanation for why only the
np.bool
type allows subscripting, and not the others?
As @riku-sakamoto correctly explained, the main use-case of np.bool
's generic type parameter, is so that static type-checkers are able to distinguish between np.True_
and np.False_
.
In scipy-stubs
, for example, this allows users to pass e.g. keepdims=np.True_
, and the return type will be inferred as e.g. Array[float64]
. If np.bool
wouldn't have been generic, then that would instead have been float64 | Array[float64]
.
But the downside of having this generic type-parameter, is that type-checkers will now display the type of a: np.bool = ...
as np.bool[bool]
. That's because the np.bool
type parameter defaults to builtins.bool
if omitted. Type-checkers like pyright and mypy will interpret np.bool
as np.bool[builtins.bool]
, and report it as such.
But they don't have to display it that way. The type-checkers could also have decided to report np.bool
as np.bool
. So the way I see it, this unnecessarily verbose formatting is not our responsibility, but that of the type-checkers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another use-case that's kinda fun (and kinda niche), is that static type-checkers now understand boolean algebra:
numpy/numpy/typing/tests/data/reveal/bitwise_ops.pyi
Lines 154 to 167 in 4e00e4d
assert_type(b0_ | b0_, np.bool[FalseType]) | |
assert_type(b0_ | b1_, np.bool[TrueType]) | |
assert_type(b1_ | b0_, np.bool[TrueType]) | |
assert_type(b1_ | b1_, np.bool[TrueType]) | |
assert_type(b0_ ^ b0_, np.bool[FalseType]) | |
assert_type(b0_ ^ b1_, np.bool[TrueType]) | |
assert_type(b1_ ^ b0_, np.bool[TrueType]) | |
assert_type(b1_ ^ b1_, np.bool[FalseType]) | |
assert_type(b0_ & b0_, np.bool[FalseType]) | |
assert_type(b0_ & b1_, np.bool[FalseType]) | |
assert_type(b1_ & b0_, np.bool[FalseType]) | |
assert_type(b1_ & b1_, np.bool[TrueType]) |
where b0_ = np.False_
and b1_ = np.True_
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def sample(must_positive_flags: np.bool[True]): ....
nitpick: you forgot the Literal
(and there are 4 dots), i.e.
def sample(must_positive_flags: np.bool[Literal[True]]): ...
Fixes numpy#29247. Implements `booleantype_class_getitem_abc` and adds a `__class_getitem__` method to `np.bool` to enable subscript access (e.g., `np.bool[int]`).
8b04fc2
to
0dbbed6
Compare
0dbbed6
to
003281b
Compare
Fixes #29247
This PR adds support for subscript access to
np.bool
by implementing a__class_getitem__
method.Implementation Details
booleantype_class_getitem_abc
, following the pattern used for other scalar types.__class_getitem__
to thenp.bool
type.@ngoldbaum
I created new PR.
Would you mind reviewing this when you get a chance? Thank you!