8000 ENH: Add a `__dict__` to ufunc objects and allow overriding `__doc__` by mtsokol · Pull Request #27735 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

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 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__"]`).
6 changes: 4 additions & 2 deletions 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 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 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 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();
/*
* 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;
}
} 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},
};


/******************************************************************************
*** 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 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
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
0