Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

ENH: Add a __dict__ to ufunc objects and allow overriding __doc__ #27735

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

Merged
merged 7 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
ENH: Add a __dict__ to ufunc objects and use it to allow overriding…
… `__doc__`

Co-authored-by: Nathan Goldbaum <nathan.goldbaum@gmail.com>
  • Loading branch information
mtsokol and ngoldbaum committed Nov 11, 2024
commit 3eafa3f34a8e1a992e2ae365d134a45a473fd3ad
2 changes: 2 additions & 0 deletions 2 doc/release/upcoming_changes/27735.new_feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* UFuncs new support `__dict__` attribute and allow overriding
`__doc__` (either directly or via `ufunc.__dict__["__doc__"]`).
rgommers marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 4 additions & 2 deletions 6 numpy/_core/include/numpy/ufuncobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,10 @@ typedef struct _tagPyUFuncObject {
* with the dtypes for the inputs and outputs.
*/
PyUFunc_TypeResolutionFunc *type_resolver;
/* Was the legacy loop resolver */
void *reserved2;

/* A dictionary to monkeypatch ufuncs */
PyObject *dict;

/*
* This was blocked off to be the "new" inner loop selector in 1.7,
* but this was never implemented. (This is also why the above
Expand Down
1 change: 1 addition & 0 deletions 1 numpy/_core/src/multiarray/npy_static_data.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ intern_strings(void)
INTERN_STRING(__dlpack__, "__dlpack__");
INTERN_STRING(pyvals_name, "UFUNC_PYVALS_NAME");
INTERN_STRING(legacy, "legacy");
INTERN_STRING(__doc__, "__doc__");
return 0;
}

Expand Down
1 change: 1 addition & 0 deletions 1 numpy/_core/src/multiarray/npy_static_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ typedef struct npy_interned_str_struct {
PyObject *__dlpack__;
PyObject *pyvals_name;
PyObject *legacy;
PyObject *__doc__;
} npy_interned_str_struct;

/*
Expand Down
45 changes: 43 additions & 2 deletions 45 numpy/_core/src/umath/ufunc_object.c
Original file line number Diff line number Diff line change
Expand Up @@ -4771,6 +4771,7 @@ PyUFunc_FromFuncAndDataAndSignatureAndIdentity(PyUFuncGenericFunction *func, voi
return NULL;
}
}
ufunc->dict = PyDict_New();
rgommers marked this conversation as resolved.
Show resolved Hide resolved
/*
* TODO: I tried adding a default promoter here (either all object for
* some special cases, or all homogeneous). Those are reasonable
Expand Down Expand Up @@ -6411,6 +6412,15 @@ ufunc_get_doc(PyUFuncObject *ufunc, void *NPY_UNUSED(ignored))
{
PyObject *doc;

// If there is a __doc__ in the instance __dict__, use it.
int result = PyDict_GetItemRef(ufunc->dict, npy_interned_str.__doc__, &doc);
if (result == -1) {
return NULL;
}
else if (result == 1) {
return doc;
}

if (npy_cache_import_runtime(
"numpy._core._internal", "_ufunc_doc_signature_formatter",
&npy_runtime_imports._ufunc_doc_signature_formatter) == -1) {
Expand All @@ -6434,6 +6444,20 @@ ufunc_get_doc(PyUFuncObject *ufunc, void *NPY_UNUSED(ignored))
return doc;
}

static int
ufunc_set_doc(PyUFuncObject *ufunc, PyObject *doc, void *NPY_UNUSED(ignored))
{
if (doc == NULL) {
int result = PyDict_Contains(ufunc->dict, npy_interned_str.__doc__);
if (result == 1) {
return PyDict_DelItem(ufunc->dict, npy_interned_str.__doc__);
} else {
return result;
}
rgommers marked this conversation as resolved.
Show resolved Hide resolved
} else {
return PyDict_SetItem(ufunc->dict, npy_interned_str.__doc__, doc);
}
}

static PyObject *
ufunc_get_nin(PyUFuncObject *ufunc, void *NPY_UNUSED(ignored))
Expand Down Expand Up @@ -6519,8 +6543,8 @@ ufunc_get_signature(PyUFuncObject *ufunc, void *NPY_UNUSED(ignored))

static PyGetSetDef ufunc_getset[] = {
{"__doc__",
(getter)ufunc_get_doc,
NULL, NULL, NULL},
(getter)ufunc_get_doc, (setter)ufunc_set_doc,
NULL, NULL},
{"nin",
(getter)ufunc_get_nin,
NULL, NULL, NULL},
Expand Down Expand Up @@ -6549,6 +6573,17 @@ static PyGetSetDef ufunc_getset[] = {
};


/******************************************************************************
*** UFUNC MEMBERS ***
*****************************************************************************/

static PyMemberDef ufunc_members[] = {
{"__dict__", T_OBJECT, offsetof(PyUFuncObject, dict),
READONLY},
{NULL},
};
seberg marked this conversation as resolved.
Show resolved Hide resolved


/******************************************************************************
*** UFUNC TYPE OBJECT ***
*****************************************************************************/
Expand All @@ -6568,6 +6603,12 @@ NPY_NO_EXPORT PyTypeObject PyUFunc_Type = {
.tp_traverse = (traverseproc)ufunc_traverse,
.tp_methods = ufunc_methods,
.tp_getset = ufunc_getset,
.tp_getattro = PyObject_GenericGetAttr,
.tp_setattro = PyObject_GenericSetAttr,
// TODO when Python 3.12 is the minimum supported version,
// use Py_TPFLAGS_MANAGED_DICT
.tp_members = ufunc_members,
.tp_dictoffset = offsetof(PyUFuncObject, dict),
};

/* End of code for ufunc objects */
20 changes: 20 additions & 0 deletions 20 numpy/_core/tests/test_umath.py
Original file line number Diff line number Diff line change
Expand Up @@ -4016,6 +4016,26 @@ def test_array_ufunc_direct_call(self):
res = a.__array_ufunc__(np.add, "__call__", a, a)
assert_array_equal(res, a + a)

def test_ufunc_docstring(self):
original_doc = np.add.__doc__
new_doc = "new docs"

np.add.__doc__ = new_doc
assert np.add.__doc__ == new_doc
assert np.add.__dict__["__doc__"] == new_doc

del np.add.__doc__
assert np.add.__doc__ == original_doc
assert np.add.__dict__ == {}

np.add.__dict__["other"] = 1
mhvk marked this conversation as resolved.
Show resolved Hide resolved
np.add.__dict__["__doc__"] = new_doc
assert np.add.__doc__ == new_doc

del np.add.__dict__["__doc__"]
assert np.add.__doc__ == original_doc


class TestChoose:
def test_mixed(self):
c = np.array([True, True])
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.