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
Making typed.List a typing Generic #5958
Conversation
@luk-f-a thanks for reporting this, I have added it to the review queue for now. |
@EPronovost would you have time to review this PR? |
@EPronovost thanks for the review. I fixed what you found, plus other similar things, eg other methods returning None which I hadn't annotated. I also completed those annotations that were incomplete, those where I had only annotated one input variable, like getitem, insert, index. |
Interesting finding from typeguard after I annotated
Reproducer (it should always return None but in some cases it's returning self) from numba.typed import List
...: my_list = List([1,2])
...: print(my_list.extend(List([1,2])))
...: print(my_list.extend(tuple()))
None
[1, 2, 1, 2] |
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 wonder if we can/should add test coverage for this beyond just using typeguard
checking. For a full test of annotations, I think you need both positive and negative examples. I've recently done similar work for typeguard
here. To keep it smaller scope we could also just have positive tests; i.e. a test file that uses List
with annotations and gets analyzed by mypy as part of CI.
I'm curious to hear what the core devs think about this additional testing for type annotations intended to be used by users. cc: @sklam @esc @stuartarchibald
Note that if we want mypy to consider these type annotations in users' projects we need to add a py.typed
file https://www.python.org/dev/peps/pep-0561/#packaging-type-information
numba/typed/typedlist.py
Outdated
@@ -377,7 +389,7 @@ def reverse(self): | |||
def copy(self): | |||
return _copy(self) | |||
|
|||
def index(self, item, start=None, stop=None): | |||
def index(self, item: T, start: int = None, stop: int = None) -> 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.
Under strict mode inferred optionals are disabled, so these should be Optional[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.
thanks, I didn't realize that strict optionals was the default, I thought that inferred optionals were the default.
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 wonder if we shouldn't consider switching to --no-strict-optional
to avoid the added verbosity. For example, index
could fit in one line (easier to read) without losing much in terms of typing (I think :int = None
is clear enough). what do you think?
def index(self, item: T, start: pt.Optional[int] = None,
stop: pt.Optional[int] = None) -> int:
vs
def index(self, item: T, start: int = None, stop: int = None) -> 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.
For internal annotations that's something we can decide on. I think for external facing annotations we should be as precise as possible to avoid incompatibilities for users who don't use --no-strict-optional
.
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.
you're right, I changed it to the explicit Optional
.
@EPronovost I made some fixes following your latest comments, and also improved other things:
Related to this last point (running mypy), the file now passes basic tests and it could included in "level 3" files. However, I didn't actually change |
I don't really understand this PEP. Does it mean that without a Update: after investigating and asking in the typing gitter, I confirmed that it is true that " without a |
Sorry for the delay. Yes to your above question. There's relevant code in the mypy module finder here: https://github.com/python/mypy/blob/master/mypy/modulefinder.py#L180-L194 |
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.
typeguard
is failing for numba.tests.test_typedlist
. On CI, the test slices didn't run any of the failing tests by "luck".
numba/typed/typedlist.py
Outdated
return _pop(self, i) | ||
|
||
def extend(self, iterable): | ||
def extend(self, iterable: pt.Sequence[T]) -> None: #type: ignore[override] |
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 read #5958 (comment) that it needs to be a Sequence
. But, typeguard
is complaining that numpy.ndarray
is not a Sequence
for 5 tests under numba.tests.test_typedlist.TestListSort
. pt.Iterable[T]
would work for typeguard
but mypy
would complain about the __len__
. The following seems to work for both checkers:
T_co = pt.TypeVar('T_co', covariant=True)
class _Sequence(pt.Protocol[T_co]):
def __getitem__(self, i: int) -> T_co: ...
def __len__(self) -> int: ...
Co-authored-by: Siu Kwan Lam <1929845+sklam@users.noreply.github.com>
Co-authored-by: Siu Kwan Lam <1929845+sklam@users.noreply.github.com>
Co-authored-by: Siu Kwan Lam <1929845+sklam@users.noreply.github.com>
Test is failing because |
what's the solution? Did you run the full suite locally? Is it possible to expand typeguard to run on more slices? |
I tried a fix but made it worse. I'll build something better tomorrow. |
Right, we will need to make more configs use typeguard. |
If we guard the protocol type for py3.8 only and use a string to refer to it, we can avoid changing the implementation of extend. something like if python_version >= (3, 8):
class _Sequence(pt.Protocol[T_co]): ...
class List:
def extend(self, iterable: "_Sequence"): ... |
I think it's ok to do type-checks on only py>=3.8. I'm seeing that |
Thanks @sklam, I'll try what you suggested above. Funny you found a 5th option, I was considering the pros/cons of:
|
This PR adds typing information to the typed List, so in can be used as a Generic in variable annotations, e.g.
While some of these would also be runtime errors, this PR allows List to be fully checked as a generic by mypy.
This is not related to checking Numba itself with mypy, this just allows users to check their typed.List code with mypy.
Besides setting List as a Generic, it also annotates the main methods (append, getitem, setitem), and the methods for protocols: Iterable[T], Sized, Container[T], and Collection[T].