Skip to content

Improved typing for __array__#138

Closed
kylebarron wants to merge 4 commits into
rasterio:mainfrom
kylebarron:kyle/__array__-typing
Closed

Improved typing for __array__#138
kylebarron wants to merge 4 commits into
rasterio:mainfrom
kylebarron:kyle/__array__-typing

Conversation

@kylebarron

Copy link
Copy Markdown
Contributor

Similar in spirit to #137 for for the __array__ conversion to numpy.

This one is less important than #137 so it's ok if you reject it for complexity:

Before:

image

After:

image
  • This effectively just changes the behavior of np.asarray when no explicit dtype is provided (assuming users call np.asarray and not Affine.__array__)
  • I like to have "complete" typing like this, but compared to Implement typing overloads for (matrix) multiplication #137 it's not as crucial to have the type checker know that the array is a float64 array specifically.

@mwtoews

mwtoews commented Feb 1, 2026

Copy link
Copy Markdown
Contributor

numpy is not a project dependency. It's currently an optional dependency for "test". Is there a way to make numpy types optional?

@kylebarron

Copy link
Copy Markdown
Contributor Author

Anything inside of if TYPE_CHECKING: doesn't get evaluated at runtime. So if the user doesn't have numpy installed and they call __array__ directly, their type checker will just infer Any / Unknown

image

@sgillies

sgillies commented Feb 2, 2026

Copy link
Copy Markdown
Member

@kylebarron thank you, but this kind of complicated typing makes me depressed.

Instead, I'm inclined to deprecate __array__ and remove the conditional numpy dependency. Instead, we could use Numpy's array interface like this:

>>> import array
>>> class Foo:
...     __array_interface__ = {"shape": (2, 2), "typestr": "d", "data": array.array("d", [1,2,3,4]), "version": 3}
... 
>>> import numpy
>>> numpy.array(Foo())
array([[1., 2.],
       [3., 4.]])

The data key properly constructing a 1-D buffer from the Affine attributes, of course. The type annotation for this should be simpler, all of the types in the array interface items being fixed, yes?

@kylebarron

kylebarron commented Feb 3, 2026

Copy link
Copy Markdown
Contributor Author

I pushed another option, which hard-codes np.float64. This will be wrong if users ever call __array__ directly, but provides correct typing for np.array and np.asarray (since numpy overrides the inferred type if an explicit dtype is provided):

image

So it's a viable option if you don't expect users to call __array__ directly.

@kylebarron thank you, but this kind of complicated typing makes me depressed.

To be clear, this is pretty low value for me, so it's totally fine if you want to reject it for complexity. (I think #137 is much higher value, since matrix multiplication is probably much more common, and the typing pain of the currently-inferred union is much worse)

Instead, I'm inclined to deprecate __array__ and remove the conditional numpy dependency. Instead, we could use Numpy's array interface like this:

I don't know enough about pros and cons of __array__ and the array interface. But they both seem like perfectly valid ways to export to Numpy.

I don't think it's possible to type that array interface because the type checker isn't able to statically match the typestr: d with an np.float64.

@mwtoews

mwtoews commented Feb 8, 2026

Copy link
Copy Markdown
Contributor

For reference, #108 added __array__ since version 3.0a1, and wouldn't really need to be deprecated if removed, since it has never been in a stable release. I'd prefer to keep it, so long as it doesn't add new dependencies.

I added this method to help cross-compare matrix algebra results from this package with numpy, e.g. several examples in test_numpy.py. This includes matrix-matrix and matrix-vector combinations.

@kylebarron

kylebarron commented Feb 9, 2026

Copy link
Copy Markdown
Contributor Author

Regardless of how you feel about this type hinting, adding __array__ makes sense to me

IIRC, I believe that np.array(affine) will function regardless, because np.array works on tuple input, but it'll result in a 1-D shape of 9, rather than a 3x3 matrix.

Comment thread src/affine/__init__.py Outdated
@sgillies

Copy link
Copy Markdown
Member

@mwtoews I'm going to trade __array__ for __array_interface__ so that we can have numpy compatibility without any dependence on the numpy package.

@kylebarron

Copy link
Copy Markdown
Contributor Author

Just my two cents: __array_interface__ seems like overkill for exposing Affine as an array. It's just a tuple of 9 numbers and should be simple for downstream users to use any array library to create a library and call reshape(3, 3).

I'd only expect __array_interface__ to be implemented at the C level for libraries that want to interoperate with large array data.

@sgillies

Copy link
Copy Markdown
Member

@kylebarron np.array(Affine()).reshape(3, 3) is not a hardship, I agree.

@sgillies

sgillies commented Mar 2, 2026

Copy link
Copy Markdown
Member

superseded by #141.

@sgillies sgillies closed this Mar 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants