Skip to content
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

Change ArrayLike type to use read only properties #289

Merged
merged 2 commits into from
Jun 1, 2023

Conversation

andy-sweet
Copy link
Member

@andy-sweet andy-sweet commented May 31, 2023

Description

The np.ndarray type does not seem to satisfy the ArrayLike type. This PR fixes that by using read-only properties instead of plain settable attributes.

Here's a simple reproducer for mypy.

import numpy as np
from npe2.types import ArrayLike

a: np.ndarray = np.array([])

def foo(b: ArrayLike) -> None:
    print(b)

foo(a)

For me, mypy fails before this change, but passes afterwards. Let me know if there's a way to add some form of this to automated tests/CI.

Context

I encountered this while writing a plugin where I'm aiming for full(ish) typing coverage. I understand the ArrayLike type has other problems with respect to some napari layer data types (which have their own problems), but it seems like it should at least be satisfied by np.ndarray. This is also more consistent with napari's layer data protocol.

@andy-sweet andy-sweet changed the title Change ArrayLike typing to use read only properties Change ArrayLike type to use read only properties May 31, 2023
@codecov
Copy link

codecov bot commented May 31, 2023

Codecov Report

Merging #289 (9d846c6) into main (b33bc09) will not change coverage.
The diff coverage is 100.00%.

@@            Coverage Diff            @@
##              main      #289   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           37        37           
  Lines         2782      2785    +3     
=========================================
+ Hits          2782      2785    +3     
Impacted Files Coverage Δ
src/npe2/types.py 100.00% <100.00%> (ø)

@aganders3
Copy link
Contributor

LGTM - I tested your fix with the provided example and confirmed your results. I agree this is the correct way to represent read-only attributes in a Protocol.

@kne42
Copy link
Member

kne42 commented May 31, 2023

is there a reason we aren't just using numpy.typing.ArrayLike?

@andy-sweet
Copy link
Member Author

andy-sweet commented May 31, 2023

is there a reason we aren't just using numpy.typing.ArrayLike?

I'd prefer that too.

Looks like there's some argument against it in napari's ArrayLike, but that may be less applicable for npe2 which is only describing parameter values for initializers (in which case the protocol here is less relevant).

Happy to change to use that, but might need some more context here.

@aganders3
Copy link
Contributor

I'm not too familiar with the history or usage here, but poking around I see that numpy.typing.ArrayLike is a bit looser and accepts things like list[list[int]].

Take this script for example - both functions return data that fail with add_image, but mypy only reports an error for foo:

import numpy as np
from napari.viewer import Viewer
from npe2.types import ArrayLike


def foo() -> ArrayLike:
    return [[1, 2, 3], [4, 5, 6]]


def bar() -> np.typing.ArrayLike:
    return [[1, 2, 3], [4, 5, 6]]


viewer = Viewer()
try:
    viewer.add_image(foo())
except Exception as e:
    print("failed to add image with `foo()`")
    print(e)

try:
    viewer.add_image(bar())
except Exception as e:
    print("failed to add image with `bar()`")
    print(e)
❯ python arraylike.py 
failed to add image with `foo()`
'list' object has no attribute 'size'
failed to add image with `bar()`
'list' object has no attribute 'size'

❯ mypy arraylike.py 
arraylike.py:7:12: error: Incompatible return value type (got "List[List[int]]", expected "ArrayLike")  [return-value]
        return [[1, 2, 3], [4, 5, 6]]
               ^~~~~~~~~~~~~~~~~~~~~~
Found 1 error in 1 file (checked 1 source file)

@tlambert03
Copy link
Collaborator

Yeah arraylike is probably too lose. It's anything that can be cast to an array (including scalars, etc)... here, we're just trying to indicate the protocol of an array object, without restricting to numpy arrays. Andy your solution is great. There are ways to test this (https://pypi.org/project/pytest-mypy-plugins/), and I have used it in other projects... but it's a pain, and adds significant maintenance burden to keep working. So let's not bother

@tlambert03
Copy link
Collaborator

tlambert03 commented Jun 1, 2023

(In other words, if you want to say something must have a shape and a dtype, then numpy arraylike won't work unless you always call np.asarray inside the body of the function... we're not implying that here with this object)

@tlambert03 tlambert03 merged commit 0f4b633 into napari:main Jun 1, 2023
@andy-sweet andy-sweet deleted the fix-arraylike-type branch June 1, 2023 14:47
@tlambert03 tlambert03 added the bug Something isn't working label Jul 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants