Skip to content

Commit

Permalink
bpo-44019: Implement operator.call().
Browse files Browse the repository at this point in the history
Having `operator.call(obj, arg)` mean `type(obj).__call__(obj, arg)` is
consistent with the other dunder operators.  The semantics with `*args,
**kwargs` then follow naturally from the single-arg semantics.
  • Loading branch information
anntzer committed Sep 23, 2021
1 parent 4f51fa9 commit 344751b
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 0 deletions.
11 changes: 11 additions & 0 deletions Doc/library/operator.rst
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,17 @@ Operations which work with sequences (some of them with mappings too) include:

.. versionadded:: 3.4


The following operation works with callables:

.. function:: call(obj, / *args, **kwargs)
__call__(obj, /, *args, **kwargs)
Return ``obj(*args, **kwargs)``.

.. versionadded:: 3.11


The :mod:`operator` module also defines tools for generalized attribute and item
lookups. These are useful for making fast field extractors as arguments for
:func:`map`, :func:`sorted`, :meth:`itertools.groupby`, or other functions that
Expand Down
7 changes: 7 additions & 0 deletions Lib/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,12 @@ def length_hint(obj, default=0):
raise ValueError(msg)
return val

# Other Operations ************************************************************#

def call(obj, /, *args, **kwargs):
"""Same as obj(*args, **kwargs)."""
return obj(*args, **kwargs)

# Generalized Lookup Objects **************************************************#

class attrgetter:
Expand Down Expand Up @@ -423,6 +429,7 @@ def ixor(a, b):
__abs__ = abs
__add__ = add
__and__ = and_
__call__ = call
__floordiv__ = floordiv
__index__ = index
__inv__ = inv
Expand Down
12 changes: 12 additions & 0 deletions Lib/test/test_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,18 @@ def __length_hint__(self):
with self.assertRaises(LookupError):
operator.length_hint(X(LookupError))

def test_call(self):
operator = self.module

def func(*args, **kwargs): return args, kwargs

self.assertEqual(operator.call(func), ((), {}))
self.assertEqual(operator.call(func, 0, 1), ((0, 1), {}))
self.assertEqual(operator.call(func, a=2, obj=3),
((), {"a": 2, "obj": 3}))
self.assertEqual(operator.call(func, 0, 1, a=2, obj=3),
((0, 1), {"a": 2, "obj": 3}))

def test_dunder_is_original(self):
operator = self.module

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
A new function ``operator.call`` has been added, such that
``operator.call(obj, *args, **kwargs) == obj(*args, **kwargs)``.
22 changes: 22 additions & 0 deletions Modules/_operator.c
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,27 @@ _operator__compare_digest_impl(PyObject *module, PyObject *a, PyObject *b)
return PyBool_FromLong(rc);
}

PyDoc_STRVAR(_operator_call__doc__,
"call($module, obj, /, *args, **kwargs)\n"
"--\n"
"\n"
"Same as obj(*args, **kwargs).");

#define _OPERATOR_CALL_METHODDEF \
{"call", (PyCFunction)(void(*)(void))_operator_call, METH_FASTCALL | METH_KEYWORDS, _operator_call__doc__},

static PyObject *
_operator_call(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
if (!_PyArg_CheckPositional("call", nargs, 1, PY_SSIZE_T_MAX)) {
return NULL;
}
return PyObject_Vectorcall(
args[0],
&args[1], (PyVectorcall_NARGS(nargs) - 1) | PY_VECTORCALL_ARGUMENTS_OFFSET,
kwnames);
}

/* operator methods **********************************************************/

static struct PyMethodDef operator_methods[] = {
Expand Down Expand Up @@ -942,6 +963,7 @@ static struct PyMethodDef operator_methods[] = {
_OPERATOR_GE_METHODDEF
_OPERATOR__COMPARE_DIGEST_METHODDEF
_OPERATOR_LENGTH_HINT_METHODDEF
_OPERATOR_CALL_METHODDEF
{NULL, NULL} /* sentinel */

};
Expand Down

0 comments on commit 344751b

Please sign in to comment.