From bd9aef9102ac9a86454337f219c123f2ec5ca0cb Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 10 Apr 2025 03:08:48 +0100 Subject: [PATCH] Implement the identity function in the operator module --- Doc/library/operator.rst | 7 ++++ Lib/operator.py | 6 +++- Lib/test/test_operator.py | 21 ++++++++++++ Lib/test/test_typing.py | 8 ----- Lib/typing.py | 3 +- ...-04-10-04-27-41.gh-issue-132341.wwXXVD.rst | 2 ++ Modules/_operator.c | 17 ++++++++++ Modules/_typingmodule.c | 32 +------------------ Modules/clinic/_operator.c.h | 11 ++++++- Modules/clinic/_typingmodule.c.h | 12 ------- 10 files changed, 64 insertions(+), 55 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-04-10-04-27-41.gh-issue-132341.wwXXVD.rst delete mode 100644 Modules/clinic/_typingmodule.c.h diff --git a/Doc/library/operator.rst b/Doc/library/operator.rst index e8e71068dd99eb..ed983691c4e960 100644 --- a/Doc/library/operator.rst +++ b/Doc/library/operator.rst @@ -384,6 +384,13 @@ expect a function argument. return caller +.. function:: identity(obj, /) + + Returns *obj* unchanged. + + .. versionadded:: next + + .. _operator-map: Mapping Operators to Functions diff --git a/Lib/operator.py b/Lib/operator.py index 1b765522f85949..5d8215f6271af0 100644 --- a/Lib/operator.py +++ b/Lib/operator.py @@ -12,7 +12,7 @@ __all__ = ['abs', 'add', 'and_', 'attrgetter', 'call', 'concat', 'contains', 'countOf', 'delitem', 'eq', 'floordiv', 'ge', 'getitem', 'gt', 'iadd', 'iand', - 'iconcat', 'ifloordiv', 'ilshift', 'imatmul', 'imod', 'imul', + 'iconcat', 'identity', 'ifloordiv', 'ilshift', 'imatmul', 'imod', 'imul', 'index', 'indexOf', 'inv', 'invert', 'ior', 'ipow', 'irshift', 'is_', 'is_none', 'is_not', 'is_not_none', 'isub', 'itemgetter', 'itruediv', 'ixor', 'le', 'length_hint', 'lshift', 'lt', 'matmul', 'methodcaller', 'mod', @@ -231,6 +231,10 @@ def length_hint(obj, default=0): # Other Operations ************************************************************# +def identity(obj, /): + """Return the argument unchanged.""" + return obj + def call(obj, /, *args, **kwargs): """Same as obj(*args, **kwargs).""" return obj(*args, **kwargs) diff --git a/Lib/test/test_operator.py b/Lib/test/test_operator.py index 1757824580e416..59a879f809eb36 100644 --- a/Lib/test/test_operator.py +++ b/Lib/test/test_operator.py @@ -614,6 +614,27 @@ class Y: pass operator.length_hint(X(2), "abc") self.assertEqual(operator.length_hint(Y(), 10), 10) + def test_identity(self): + operator = self.module + a = 'spam' + b = [] + b.append(b) + c = None + d = object() + try: + 1/0 + except ZeroDivisionError as exc: + e = exc + f = {a, c, d, 'ham' * 1000} + + self.assertRaises(TypeError, operator.identity) + self.assertIs(operator.identity(a), a) + self.assertIs(operator.identity(b), b) + self.assertIs(operator.identity(c), c) + self.assertIs(operator.identity(d), d) + self.assertIs(operator.identity(e), e) + self.assertIs(operator.identity(f), f) + def test_call(self): operator = self.module diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index edf3cf9d4a3658..218e315a603921 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -7868,14 +7868,6 @@ def foo(a: A) -> Optional[BaseException]: self.assertIsNone(foo(None)) -class TestModules(TestCase): - func_names = ['_idfunc'] - - def test_c_functions(self): - for fname in self.func_names: - self.assertEqual(getattr(typing, fname).__module__, '_typing') - - class NewTypeTests(BaseTestCase): @classmethod def setUpClass(cls): diff --git a/Lib/typing.py b/Lib/typing.py index e67284fc571b1b..cadf4e4ba7519c 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -30,7 +30,6 @@ from types import GenericAlias from _typing import ( - _idfunc, TypeVar, ParamSpec, TypeVarTuple, @@ -3359,7 +3358,7 @@ def name_by_id(user_id: UserId) -> str: num = UserId(5) + 1 # type: int """ - __call__ = _idfunc + __call__ = operator.identity def __init__(self, name, tp): self.__qualname__ = name diff --git a/Misc/NEWS.d/next/Library/2025-04-10-04-27-41.gh-issue-132341.wwXXVD.rst b/Misc/NEWS.d/next/Library/2025-04-10-04-27-41.gh-issue-132341.wwXXVD.rst new file mode 100644 index 00000000000000..75567d33aa97ed --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-10-04-27-41.gh-issue-132341.wwXXVD.rst @@ -0,0 +1,2 @@ +Add :func:`operator.identity`, the identity function. +Patch by Adam Turner. diff --git a/Modules/_operator.c b/Modules/_operator.c index 1cc05c39f5dbad..0c3ad044602d13 100644 --- a/Modules/_operator.c +++ b/Modules/_operator.c @@ -915,6 +915,22 @@ _operator__compare_digest_impl(PyObject *module, PyObject *a, PyObject *b) return PyBool_FromLong(rc); } +/*[clinic input] +_operator.identity -> object + + obj: object + / + +Return the argument unchanged. +[clinic start generated code]*/ + +static PyObject * +_operator_identity(PyObject *module, PyObject *obj) +/*[clinic end generated code: output=deede47105fe0dd8 input=72f5a615c2a823c2]*/ +{ + return Py_NewRef(obj); +} + PyDoc_STRVAR(_operator_call__doc__, "call($module, obj, /, *args, **kwargs)\n" "--\n" @@ -994,6 +1010,7 @@ static struct PyMethodDef operator_methods[] = { _OPERATOR_GE_METHODDEF _OPERATOR__COMPARE_DIGEST_METHODDEF _OPERATOR_LENGTH_HINT_METHODDEF + _OPERATOR_IDENTITY_METHODDEF _OPERATOR_CALL_METHODDEF {NULL, NULL} /* sentinel */ diff --git a/Modules/_typingmodule.c b/Modules/_typingmodule.c index e51279c808a2e1..d41824d9fd144b 100644 --- a/Modules/_typingmodule.c +++ b/Modules/_typingmodule.c @@ -9,36 +9,6 @@ #include "internal/pycore_typevarobject.h" #include "internal/pycore_unionobject.h" // _PyUnion_Type #include "pycore_pystate.h" // _PyInterpreterState_GET() -#include "clinic/_typingmodule.c.h" - -/*[clinic input] -module _typing - -[clinic start generated code]*/ -/*[clinic end generated code: output=da39a3ee5e6b4b0d input=1db35baf1c72942b]*/ - -/* helper function to make typing.NewType.__call__ method faster */ - -/*[clinic input] -_typing._idfunc -> object - - x: object - / - -[clinic start generated code]*/ - -static PyObject * -_typing__idfunc(PyObject *module, PyObject *x) -/*[clinic end generated code: output=63c38be4a6ec5f2c input=49f17284b43de451]*/ -{ - return Py_NewRef(x); -} - - -static PyMethodDef typing_methods[] = { - _TYPING__IDFUNC_METHODDEF - {NULL, NULL, 0, NULL} -}; PyDoc_STRVAR(typing_doc, "Primitives and accelerators for the typing module.\n"); @@ -85,7 +55,7 @@ static struct PyModuleDef typingmodule = { "_typing", typing_doc, 0, - typing_methods, + NULL, _typingmodule_slots, NULL, NULL, diff --git a/Modules/clinic/_operator.c.h b/Modules/clinic/_operator.c.h index 48a8ea8c3379ab..77f5d21e2b3e27 100644 --- a/Modules/clinic/_operator.c.h +++ b/Modules/clinic/_operator.c.h @@ -1507,4 +1507,13 @@ _operator__compare_digest(PyObject *module, PyObject *const *args, Py_ssize_t na exit: return return_value; } -/*[clinic end generated code: output=972e2543c4fcf1ba input=a9049054013a1b77]*/ + +PyDoc_STRVAR(_operator_identity__doc__, +"identity($module, obj, /)\n" +"--\n" +"\n" +"Return the argument unchanged."); + +#define _OPERATOR_IDENTITY_METHODDEF \ + {"identity", (PyCFunction)_operator_identity, METH_O, _operator_identity__doc__}, +/*[clinic end generated code: output=6e375ead0c0a6464 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_typingmodule.c.h b/Modules/clinic/_typingmodule.c.h deleted file mode 100644 index ea415e67153ed8..00000000000000 --- a/Modules/clinic/_typingmodule.c.h +++ /dev/null @@ -1,12 +0,0 @@ -/*[clinic input] -preserve -[clinic start generated code]*/ - -PyDoc_STRVAR(_typing__idfunc__doc__, -"_idfunc($module, x, /)\n" -"--\n" -"\n"); - -#define _TYPING__IDFUNC_METHODDEF \ - {"_idfunc", (PyCFunction)_typing__idfunc, METH_O, _typing__idfunc__doc__}, -/*[clinic end generated code: output=e7ea2a3cb7ab301a input=a9049054013a1b77]*/