<div dir="ltr">I think that with your comments in mind, it may just be best to embrace duck typing, like Matthew suggested. I propose the following workflow:<br><ul><li>__array_concatenate__ and similar "protocol" functions return NotImplemented if they won't work.</li><li>"Base functions" that can be called directly like __getitem__ raise NotImplementedError if they won't work.</li><li>__arrayish__ = True<br></li></ul><p>Then, something like np.concatenate would do the following:</p><ul><li>Call __array_concatenate__ following the same order as ufunc arguments.</li><li>If everything fails, raise NotImplementedError (or convert everything to ndarray).</li></ul><p>Overloaded functions would do something like this (perhaps a simple decorator will do for the repetitive work?):</p><ul><li>Try with np.arrayish</li><li>Catch NotImplementedError</li><ul><li>Try with np.array</li></ul></ul><p>Then, we use abstract classes just to overload functionality or implement things in terms of others. If something fails, we have a decent fallback. We don't need to do anything special in order to "check" functionality.</p><p>Feel free to propose changes, but this is the best I could come up with that would require the smallest incremental changes to Numpy while also supporting everything right from the start.<br></p></div><div class="gmail_extra"><br><div class="gmail_quote">On Thu, Mar 22, 2018 at 9:14 AM, Nathaniel Smith <span dir="ltr"><<a href="mailto:njs@pobox.com" target="_blank">njs@pobox.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span class="">On Sat, Mar 10, 2018 at 4:27 AM, Matthew Rocklin <<a href="mailto:mrocklin@gmail.com">mrocklin@gmail.com</a>> wrote:<br>
> I'm very glad to see this discussion.<br>
><br>
> I think that coming up with a single definition of array-like may be<br>
> difficult, and that we might end up wanting to embrace duck typing instead.<br>
><br>
> It seems to me that different array-like classes will implement different<br>
> mixtures of features.  It may be difficult to pin down a single definition<br>
> that includes anything except for the most basic attributes (shape and<br>
> dtype?).  Consider two extreme cases of restrictive functionality:<br>
><br>
> LinearOperators (support dot in a numpy-like way)<br>
> Storage objects like h5py (support getitem in a numpy-like way)<br>
><br>
> I can imagine authors of both groups saying that they should qualify as<br>
> array-like because downstream projects that consume them should not convert<br>
> them to numpy arrays in important contexts.<br>
<br>
</span>I think this is an important point -- there are a lot of subtleties in<br>
the interfaces that different objects might want to provide. Some<br>
interesting ones that haven't been mentioned:<br>
<br>
- a "duck array" that has everything except fancy indexing<br>
- xarray's arrays are just like numpy arrays in most ways, but they<br>
have incompatible broadcasting semantics<br>
- immutable vs. mutable arrays<br>
<br>
When faced with this kind of situation, always it's tempting to try to<br>
write down some classification system to capture every possible<br>
configuration of interesting behavior. In fact, this is one of the<br>
most classic nerd snipes; it's been catching people for literally<br>
thousands of years [1]. Most of these attempts fail though :-).<br>
<br>
So let's back up -- I probably erred in not making this more clear in<br>
the NEP, but I actually have a fairly concrete use case in mind here.<br>
What happened is, I started working on a NEP for<br>
__array_concatenate__, and my thought pattern went as follows:<br>
<br>
1) Cool, this should work for np.concatenate.<br>
2) But what about all the other variants, like np.row_stack. We don't<br>
want __array_row_stack__; we want to express row_stack in terms of<br>
concatenate.<br>
3) Ok, what's row_stack? It's:<br>
  np.concatenate([np.atleast_2d(<wbr>arr) for arr in arrs], axis=0)<br>
4) So I need to make atleast_2d work on duck arrays. What's<br>
atleast_2d? It's: asarray + some shape checks and indexing with<br>
newaxis<br>
5) Okay, so I need something atleast_2d can call instead of asarray [2].<br>
<br>
And this kind of pattern shows up everywhere inside numpy, e.g. it's<br>
the first thing inside lots of functions in np.linalg b/c they do some<br>
futzing with dtypes and shape before delegating to ufuncs, it's the<br>
first thing the mean() function does b/c it needs to check arr.dtype<br>
before proceeding, etc. etc.<br>
<br>
So, we need something we can use in these functions as a first step<br>
towards unlocking the use of duck arrays in general. But we can't<br>
realistically go through each of these functions, make an exact list<br>
of all the operations/attributes it cares about, and then come up with<br>
exactly the right type constraint for it to impose at the top. And<br>
these functions aren't generally going to work on LinearOperators or<br>
h5py datasets anyway.<br>
<br>
We also don't want to go through every function in numpy and add new<br>
arguments to control this coercion behavior.<br>
<br>
What we can do, at least to start, is to have a mechanism that passes<br>
through objects that aspire to be "complete" duck arrays, like dask<br>
arrays or sparse arrays or astropy's unit arrays, and then if it turns<br>
out that in practice people find uses for finer-grained distinctions,<br>
we can iteratively add those as a second pass. Notice that if a<br>
function starts out requiring a "complete" duck array, and then later<br>
relaxes that to accept "partial" duck arrays, that's actually<br>
increasing the domain of objects that it can act on, so it's a<br>
backwards-compatible change that we can do later.<br>
<br>
So I think we should start out with a concept of "duck array" that's<br>
fairly strong but a bit vague on the exact details (e.g.,<br>
dask.array.Array is currently missing some weird things like arr.ptp()<br>
and arr.tolist(), I guess because no-one has ever noticed or cared?).<br>
<br>
------------<br>
<br>
Thinking things through like this, I also realized that this proposal<br>
jumps through hoops to avoid changing np.asarray itself, because I was<br>
nervous about changing the rule that its output is always an<br>
ndarray... but actually, this is currently the rule for most functions<br>
in numpy, and the whole point of this proposal is to relax that rule<br>
for most functions, in cases where the user is explicitly passing in a<br>
duck-array object. So maybe I'm being overparanoid? I'm genuinely<br>
unsure here.<br>
<br>
Instead of messing about with ABCs, an alternative mechanism would be<br>
to add a new method __arrayish__ (hat tip to Tom Caswell for the name<br>
:-)), that essentially acts as an override for Python-level calls to<br>
np.array / np.asarray, in much the same way that __array_ufunc__<br>
overrides ufuncs, etc. (C level calls to PyArray_FromAny and similar<br>
would of course continue to return ndarray objects, and I assume we'd<br>
add some argument like require_ndarray= that you could pass to<br>
explicitly indicate whether you needed C-level compatibility.)<br>
<br>
This would also allow objects like h5py datasets to *produce* an<br>
arrayish object on demand, even if they aren't one themselves. (E.g.,<br>
imagine some hdf5-like storage that holds sparse arrays instead of<br>
regular arrays.)<br>
<br>
I'm thinking I may write this option up as a second NEP, to compete<br>
with my first one.<br>
<br>
-n<br>
<br>
[1] See: <a href="https://www.wiley.com/en-us/The+Search+for+the+Perfect+Language-p-9780631205104" rel="noreferrer" target="_blank">https://www.wiley.com/en-us/<wbr>The+Search+for+the+Perfect+<wbr>Language-p-9780631205104</a><br>
[2] Actually atleast_2d calls asanyarray, not asarray, but that's just<br>
a detail; the way to solve this problem for asanyarray is to first<br>
solve it for asarray.<br>
<div class="HOEnZb"><div class="h5"><br>
--<br>
Nathaniel J. Smith -- <a href="https://vorpus.org" rel="noreferrer" target="_blank">https://vorpus.org</a><br>
______________________________<wbr>_________________<br>
NumPy-Discussion mailing list<br>
<a href="mailto:NumPy-Discussion@python.org">NumPy-Discussion@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/numpy-discussion" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>mailman/listinfo/numpy-<wbr>discussion</a><br>
</div></div></blockquote></div><br></div>