<p dir="auto">There are several functions for which I would like to see protocols constructed.  I've raised issues for <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="321912435" data-permission-text="Title is private" data-url="https://github.com/numpy/numpy/issues/11074" data-hovercard-type="issue" data-hovercard-url="/numpy/numpy/issues/11074/hovercard" href="https://github.com/numpy/numpy/issues/11074">#11074</a>  and <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="324960360" data-permission-text="Title is private" data-url="https://github.com/numpy/numpy/issues/11128" data-hovercard-type="issue" data-hovercard-url="/numpy/numpy/issues/11128/hovercard" href="https://github.com/numpy/numpy/issues/11128">#11128</a> but these are just special cases of a much larger issue that includes many operations. The sense I've gotten is that the process to change numpy takes a while, so I'm inclined to find a catch-all solution that can serve as a catch-all while things evolve.</p>
<p dir="auto">To that end I propose that duck-arrays include a method that returns a module that mimics the numpy namespace</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="class ndarray:
    def __array_module__(self):
        import numpy as np
        return np    

class DaskArray:
    def __array_module__(self):
        import dask.array as da
        return da
        
class CuPyArray:
    def __array_module__(self):
        import cupy as cp
        return cp

class SparseArray:
    def __array_module__(self):
        import sparse
        return sparse
..."><pre class="notranslate"><span class="pl-k">class</span> <span class="pl-s1">ndarray</span>:
    <span class="pl-k">def</span> <span class="pl-en">__array_module__</span>(<span class="pl-s1">self</span>):
        <span class="pl-k">import</span> <span class="pl-s1">numpy</span> <span class="pl-k">as</span> <span class="pl-s1">np</span>
        <span class="pl-k">return</span> <span class="pl-s1">np</span>    

<span class="pl-k">class</span> <span class="pl-v">DaskArray</span>:
    <span class="pl-k">def</span> <span class="pl-en">__array_module__</span>(<span class="pl-s1">self</span>):
        <span class="pl-k">import</span> <span class="pl-s1">dask</span>.<span class="pl-s1">array</span> <span class="pl-k">as</span> <span class="pl-s1">da</span>
        <span class="pl-k">return</span> <span class="pl-s1">da</span>
        
<span class="pl-k">class</span> <span class="pl-v">CuPyArray</span>:
    <span class="pl-k">def</span> <span class="pl-en">__array_module__</span>(<span class="pl-s1">self</span>):
        <span class="pl-k">import</span> <span class="pl-s1">cupy</span> <span class="pl-k">as</span> <span class="pl-s1">cp</span>
        <span class="pl-k">return</span> <span class="pl-s1">cp</span>

<span class="pl-k">class</span> <span class="pl-v">SparseArray</span>:
    <span class="pl-k">def</span> <span class="pl-en">__array_module__</span>(<span class="pl-s1">self</span>):
        <span class="pl-k">import</span> <span class="pl-s1">sparse</span>
        <span class="pl-k">return</span> <span class="pl-s1">sparse</span>
...</pre></div>
<p dir="auto">Then, in various functions like stack or concatenate we check for these modules</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="def stack(args, **kwargs):
    modules = {arg.__array_module__() for arg in args}
    if len(modules) == 1:
        module = list(modules)[0]
        if module != numpy:
            return module.stack(args, **kwargs)
    ..."><pre class="notranslate"><span class="pl-k">def</span> <span class="pl-en">stack</span>(<span class="pl-s1">args</span>, <span class="pl-c1">**</span><span class="pl-s1">kwargs</span>):
    <span class="pl-s1">modules</span> <span class="pl-c1">=</span> {<span class="pl-s1">arg</span>.<span class="pl-en">__array_module__</span>() <span class="pl-k">for</span> <span class="pl-s1">arg</span> <span class="pl-c1">in</span> <span class="pl-s1">args</span>}
    <span class="pl-k">if</span> <span class="pl-en">len</span>(<span class="pl-s1">modules</span>) <span class="pl-c1">==</span> <span class="pl-c1">1</span>:
        <span class="pl-s1">module</span> <span class="pl-c1">=</span> <span class="pl-en">list</span>(<span class="pl-s1">modules</span>)[<span class="pl-c1">0</span>]
        <span class="pl-k">if</span> <span class="pl-s1">module</span> <span class="pl-c1">!=</span> <span class="pl-s1">numpy</span>:
            <span class="pl-k">return</span> <span class="pl-s1">module</span>.<span class="pl-en">stack</span>(<span class="pl-s1">args</span>, <span class="pl-c1">**</span><span class="pl-s1">kwargs</span>)
    ...</pre></div>
<p dir="auto">There are likely several things wrong the implementation above, but my hope is that it gets a general point across that we'll dispatch wholesale to the module of the provided duck arrays.</p>
<p dir="auto">cc <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/shoyer/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/shoyer">@shoyer</a> <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/hameerabbasi/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/hameerabbasi">@hameerabbasi</a> <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/njsmith/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/njsmith">@njsmith</a> <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/ericmjl/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/ericmjl">@ericmjl</a></p>
<h3 dir="auto">Reproducing code example:</h3>

<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="import numpy as np
z = np.ones((2, 3, 4))
z.sum((0, 0, 0))"><pre class="notranslate"><span class="pl-k">import</span> <span class="pl-s1">numpy</span> <span class="pl-k">as</span> <span class="pl-s1">np</span>
<span class="pl-s1">z</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">ones</span>((<span class="pl-c1">2</span>, <span class="pl-c1">3</span>, <span class="pl-c1">4</span>))
<span class="pl-s1">z</span>.<span class="pl-en">sum</span>((<span class="pl-c1">0</span>, <span class="pl-c1">0</span>, <span class="pl-c1">0</span>))</pre></div>

<h3 dir="auto">Error message:</h3>


<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="ValueError: duplicate value in 'axis'"><pre class="notranslate"><code class="notranslate">ValueError: duplicate value in 'axis'
</code></pre></div>
<p dir="auto">Even though I could do:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="z.sum(0, keepdims=True).sum(0, keepdims=True).sum(0, keepdims=True)"><pre class="notranslate"><span class="pl-s1">z</span>.<span class="pl-en">sum</span>(<span class="pl-c1">0</span>, <span class="pl-s1">keepdims</span><span class="pl-c1">=</span><span class="pl-c1">True</span>).<span class="pl-en">sum</span>(<span class="pl-c1">0</span>, <span class="pl-s1">keepdims</span><span class="pl-c1">=</span><span class="pl-c1">True</span>).<span class="pl-en">sum</span>(<span class="pl-c1">0</span>, <span class="pl-s1">keepdims</span><span class="pl-c1">=</span><span class="pl-c1">True</span>)</pre></div>
<p dir="auto">This happens to be caused by <a href="https://github.com/numpy/numpy/blob/8db1b077736e8418c60f2b802dab461be13f189d/numpy/core/src/multiarray/conversion_utils.c#L265-L280">this snippet</a>.</p>
<div class="highlight highlight-source-c notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="            PyObject *tmp = PyTuple_GET_ITEM(axis_in, i);
            int axis = PyArray_PyIntAsInt_ErrMsg(tmp,
                          &quot;integers are required for the axis tuple elements&quot;);
            if (error_converting(axis)) {
                return NPY_FAIL;
            }
            if (check_and_adjust_axis(&amp;axis, ndim) &lt; 0) {
                return NPY_FAIL;
            }
            if (out_axis_flags[axis]) {
                PyErr_SetString(PyExc_ValueError,
                        &quot;duplicate value in 'axis'&quot;);
                return NPY_FAIL;
            }
            out_axis_flags[axis] = 1;"><pre class="notranslate">            <span class="pl-smi">PyObject</span> <span class="pl-c1">*</span><span class="pl-s1">tmp</span> <span class="pl-c1">=</span> <span class="pl-en">PyTuple_GET_ITEM</span>(<span class="pl-s1">axis_in</span>, <span class="pl-s1">i</span>);
            <span class="pl-smi">int</span> <span class="pl-s1">axis</span> <span class="pl-c1">=</span> <span class="pl-en">PyArray_PyIntAsInt_ErrMsg</span>(<span class="pl-s1">tmp</span>,
                          <span class="pl-s">"integers are required for the axis tuple elements"</span>);
            <span class="pl-k">if</span> (<span class="pl-en">error_converting</span>(<span class="pl-s1">axis</span>)) {
                <span class="pl-k">return</span> <span class="pl-c1">NPY_FAIL</span>;
            }
            <span class="pl-k">if</span> (<span class="pl-en">check_and_adjust_axis</span>(<span class="pl-c1">&amp;</span><span class="pl-s1">axis</span>, <span class="pl-s1">ndim</span>) <span class="pl-c1">&lt;</span> <span class="pl-c1">0</span>) {
                <span class="pl-k">return</span> <span class="pl-c1">NPY_FAIL</span>;
            }
            <span class="pl-k">if</span> (<span class="pl-s1">out_axis_flags</span>[<span class="pl-s1">axis</span>]) {
                <span class="pl-en">PyErr_SetString</span>(<span class="pl-s1">PyExc_ValueError</span>,
                        <span class="pl-s">"duplicate value in 'axis'"</span>);
                <span class="pl-k">return</span> <span class="pl-c1">NPY_FAIL</span>;
            }
            <span class="pl-s1">out_axis_flags</span>[<span class="pl-s1">axis</span>] <span class="pl-c1">=</span> <span class="pl-c1">1</span>;</pre></div>
<p dir="auto">I'm not sure why do you prevent duplicate axis?</p>
<h3 dir="auto">Numpy/Python version information:</h3>

<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="1.16.2 3.6.8 |Anaconda, Inc.| (default, Dec 30 2018, 01:22:34) 
[GCC 7.3.0]"><pre class="notranslate"><code class="notranslate">1.16.2 3.6.8 |Anaconda, Inc.| (default, Dec 30 2018, 01:22:34) 
[GCC 7.3.0]
</code></pre></div>