-
Notifications
You must be signed in to change notification settings - Fork 47
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
Add an Array Protocol & improve static typing support #589
base: main
Are you sure you want to change the base?
Conversation
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.
Thanks for your work on this :)
The Array
protocl stuff looks grand. And personally I like having a DType
class just for consistency, even if practically type hinting an object has __eq__
isn't useful.
On namespace type hints (e.g. _CreatoinFuncs
, ArrayAPINamespace
): IMO I'm not sure on this yet, as they make contributing to the spec less ergonomic given we repeat the signatures, although they are pretty nifty to have. Is there a world where these could be dynamically construct them from the stubs instead? We might want to put these in a follow-up PR if just to ship the concretely non-controversial stuff first—interested in what others think.
|
||
@property | ||
def ndim(self: array) -> int: | ||
def ndim(self) -> int: |
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.
So I see methods/properties which don't return arrays don't get any type hint for their self
arg—could these be hinted with Array
?
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.
Static type checkers don't like this because the TypeVar
is not properly bound — see https://peps.python.org/pep-0484/#scoping-rules-for-type-variables ("Unbound type variables should not appear in the bodies of generic functions, or in the class bodies apart from method definitions:").
In general, there's no need to type hint the self arg unless the return type depends on the type of self — e.g. see https://peps.python.org/pep-0484/#user-defined-generic-types.
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.
A nice thing about using Protocol is that self
is understood by static type checkers to be the Protocol
.
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.
Why is this only done for certain methods?
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 did it for any methods I looked at over the course of the PR. This is by no means comprehensive and should be done: here or elsewhere.
I was thinking the same thing last evening about moving this to a followup PR.
Maybe? I haven't experimented yet but perhaps mypy will be satisfied with: class ArrayAPINamespace(Protocol):
zeros_like = staticmethod(creation_funcs.zeros_like) Also for a future PR: class Array(Protocol):
def __array_namespace__(self, ...) -> ArrayAPINamespace[Self]: ...
class ArrayAPINamespace(Protocol[Array]):
zeros_like = staticmethod(zeros_like)
class zeros_like(Protocol[Array]):
def __call__(self, x: Array, /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> Array: ... The advantage of this is that functions can be checked to see if their signature is compatible with |
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.
Thanks @nstarman, and apologies for the delay in reviewing (your PR came in right at the start of my 2 week holiday). This is looking quite good. I pushed a couple of small commits to resolve some Mypy and Sphinx complaints.
This is close to ready I'd say. One thing that isn't nice is that Self
now shows up instead of array
in the signatures of the html docs. I tried to fiddle with autodoc_type_aliases
in src/_array_api_conf.py
, but was not successful in mapping that back to array
. Using "self" will be pretty confusing in docs for regular methods that return arrays. I think we'll have to figure that one out.
The DType
protocol is fine with me - it won't add much to type-checking, but it should be fine to have it here for consistency.
The Array
protocol looks good.
@BvB93 if you have any feedback on this PR, that would be great to see. |
@nstarman there are 3 errors left when running |
If the
That sounds like a good idea. I can try adding to the CI and create an ignore file that can later be whittled down as the package is made more type friendly. |
That works. It's not clickable - there is no array object in the standard on purpose, because in the end we don't care whether it's named |
c4d977e
to
9d50eb5
Compare
@rgommers Sorry for the long interlude. I can't seem to resolve 3 linking errors in the docs https://app.circleci.com/pipelines/github/data-apis/array-api/1088/workflows/ed2dfa75-c3c7-4845-8f28-e01fe35d0c8d/jobs/996?invite=true#step-103-1646 |
Sphinx is trying to create links for the type hints. I think to fix that you need to update the list(s) here https://github.com/data-apis/array-api/blob/main/src/_array_api_conf.py#L52-L77 |
I can squash commits if you don't use Squash & Merge (assuming this is approved, of course :) ). |
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.
Nice work!
|
||
@property | ||
def shape(self: array) -> Tuple[Optional[int], ...]: | ||
def shape(self) -> tuple[int | None, ...]: |
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.
Just to say more for myself than anyone else—I initially wasn't too sure if we wanted to use the py3.9+ type hint features, but:
- Forgot that
from future import __annotations__
makes this work for py3.8 - py3.8 doesn't get updates end of next year
- The scientific ecosystem already is adopting py3.9+
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.
Looks good on my end!
I think pre-commit
is a good addition (would love to add hooks for other spec things myself), but probably not too useful until we get a workflow that runs it. That said, I'm not too sure if we'd want to do that just yet 🤔
|
Thanks @honno for the reviews and discussion. |
Any reason to change |
It would be great to see this released. It looks very developed to me, and would be useful for us within xarray. |
@@ -793,7 +793,7 @@ def vector_norm( | |||
*, | |||
axis: Optional[Union[int, Tuple[int, ...]]] = None, | |||
keepdims: bool = False, | |||
ord: Union[int, float, Literal[inf, -inf]] = 2, | |||
ord: Union[int, float, Literal[inf, -inf]] = 2, # type: ignore |
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.
Perhaps this is out of scope, but this Literal
is redundant (and as this change implies, also incorrect), since inf
and -inf
are both assignable to float
.
@rgommers. I'd be happy to finish this PR. I don't recall why this stalled out. |
Signed-off-by: nstarman <[email protected]>
Sphinx doesn't set `TYPE_CHECKING`, but does use the type annotations. `Self` is unknown to Sphinx, so should be filtered out to prevent lots of errors.
Signed-off-by: nstarman <[email protected]>
Signed-off-by: nstarman <[email protected]>
Signed-off-by: nstarman <[email protected]>
Signed-off-by: nstarman <[email protected]>
9c47664
to
fba7da1
Compare
Signed-off-by: nstarman <[email protected]>
Rebased and applied fixes for things that had changed. |
Continuing from #584, I've done a quick refactor of the array (and dtype) objects to make them typing Protocols.
@rgommers, I agree that making Array generic wrt the dtype is good for a followup.
It's not perfect, but mypy doesn't complain a whole lot, so I think this is a good start. I'm happy to make refinements.