From 6587fc60d447603fb8c631d81d9bb379f53c39ab Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 24 Sep 2021 17:22:49 +0200 Subject: bpo-44019: Implement operator.call(). (GH-27888) 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. --- Doc/library/operator.rst | 11 +++++++++++ Doc/whatsnew/3.11.rst | 8 ++++++++ Lib/operator.py | 7 +++++++ Lib/test/test_operator.py | 12 ++++++++++++ .../2021-08-22-13-25-17.bpo-44019.BN8HDy.rst | 2 ++ Modules/_operator.c | 22 ++++++++++++++++++++++ 6 files changed, 62 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2021-08-22-13-25-17.bpo-44019.BN8HDy.rst diff --git a/Doc/library/operator.rst b/Doc/library/operator.rst index 0cdba68..146cabc 100644 --- a/Doc/library/operator.rst +++ b/Doc/library/operator.rst @@ -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 diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 7e041f2..0e56b46 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -205,6 +205,14 @@ math Dickinson in :issue:`44339`.) +operator +-------- + +* A new function ``operator.call`` has been added, such that + ``operator.call(obj, *args, **kwargs) == obj(*args, **kwargs)``. + (Contributed by Antony Lee in :issue:`44019`.) + + os -- diff --git a/Lib/operator.py b/Lib/operator.py index 241fdbb..72105be 100644 --- a/Lib/operator.py +++ b/Lib/operator.py @@ -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: @@ -423,6 +429,7 @@ __not__ = not_ __abs__ = abs __add__ = add __and__ = and_ +__call__ = call __floordiv__ = floordiv __index__ = index __inv__ = inv diff --git a/Lib/test/test_operator.py b/Lib/test/test_operator.py index b9b8f15..cf3439f 100644 --- a/Lib/test/test_operator.py +++ b/Lib/test/test_operator.py @@ -518,6 +518,18 @@ class OperatorTestCase: 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 diff --git a/Misc/NEWS.d/next/Library/2021-08-22-13-25-17.bpo-44019.BN8HDy.rst b/Misc/NEWS.d/next/Library/2021-08-22-13-25-17.bpo-44019.BN8HDy.rst new file mode 100644 index 0000000..37556d7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-08-22-13-25-17.bpo-44019.BN8HDy.rst @@ -0,0 +1,2 @@ +A new function ``operator.call`` has been added, such that +``operator.call(obj, *args, **kwargs) == obj(*args, **kwargs)``. diff --git a/Modules/_operator.c b/Modules/_operator.c index f051513..12a5bf6 100644 --- a/Modules/_operator.c +++ b/Modules/_operator.c @@ -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[] = { @@ -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 */ }; -- cgit v0.12