Skip to content

Commit

Permalink
BUG: Impelement ArrayFunctionDispatcher.__get__
Browse files Browse the repository at this point in the history
While functions should not normally need this, Python functions do
provide it (C functions do not, but we are a fatter object anyway).

By implementing `__get__` we also ensure that `inspect.isroutine()`
passes.  And by that we ensure that Sphinx considers these a `py:function:`
role.

Closes gh-23032
  • Loading branch information
seberg committed Jan 18, 2023
1 parent b2badd7 commit c7b1269
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 1 deletion.
17 changes: 16 additions & 1 deletion numpy/core/src/multiarray/arrayfunction_override.c
Expand Up @@ -637,6 +637,19 @@ dispatcher_repr(PyObject *self)
return PyUnicode_FromFormat("<function %S at %p>", name, self);
}


static PyObject *
func_dispatcher___get__(PyObject *self, PyObject *obj, PyObject *cls)
{
if (obj == NULL) {
/* Act like a static method, no need to bind */
Py_INCREF(self);
return self;
}
return PyMethod_New(self, obj);
}


static PyObject *
dispatcher_get_implementation(
PyArray_ArrayFunctionDispatcherObject *self, void *NPY_UNUSED(closure))
Expand Down Expand Up @@ -677,9 +690,11 @@ NPY_NO_EXPORT PyTypeObject PyArrayFunctionDispatcher_Type = {
.tp_new = (newfunc)dispatcher_new,
.tp_str = (reprfunc)dispatcher_str,
.tp_repr = (reprfunc)dispatcher_repr,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_VECTORCALL,
.tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_VECTORCALL
| Py_TPFLAGS_METHOD_DESCRIPTOR),
.tp_methods = func_dispatcher_methods,
.tp_getset = func_dispatcher_getset,
.tp_descr_get = func_dispatcher___get__,
.tp_call = &PyVectorcall_Call,
.tp_vectorcall_offset = offsetof(PyArray_ArrayFunctionDispatcherObject, vectorcall),
};
33 changes: 33 additions & 0 deletions numpy/core/tests/test_overrides.py
Expand Up @@ -690,3 +690,36 @@ def test_like_as_none(self, function, args, kwargs):
array_like.fill(1)
expected.fill(1)
assert_equal(array_like, expected)


@requires_array_function
def test_function_like():
# We provide a `__get__` implementation, make sure it works
assert type(np.mean) is np.core._multiarray_umath._ArrayFunctionDispatcher

class MyClass:
def __array__(self):
# valid argument to mean:
return np.arange(3)

func1 = staticmethod(np.mean)
func2 = np.mean
func3 = classmethod(np.mean)

m = MyClass()
assert m.func1([10]) == 10
assert m.func2() == 1 # mean of the arange
with pytest.raises(TypeError, match="unsupported operand type"):
# Tries to operate on the class
m.func3()

# Manual binding also works (the above may shortcut):
bound = np.mean.__get__(m, MyClass)
assert bound() == 1

bound = np.mean.__get__(None, MyClass) # unbound actually
assert bound([10]) == 10

bound = np.mean.__get__(MyClass) # classmethod
with pytest.raises(TypeError, match="unsupported operand type"):
bound()

0 comments on commit c7b1269

Please sign in to comment.