diff --git a/Doc/library/operator.rst b/Doc/library/operator.rst index a9a6026af406fe..bd79039ccb2249 100644 --- a/Doc/library/operator.rst +++ b/Doc/library/operator.rst @@ -208,6 +208,20 @@ Operations which work with sequences (some of them with mappings too) include: Return ``a + b`` for *a* and *b* sequences. +.. function:: in_(a, b) + + Return the outcome of the test ``a in b``. + + .. versionadded:: 3.14 + + +.. function:: not_in(a, b) + + Return the outcome of the test ``a not in b``. + + .. versionadded:: 3.14 + + .. function:: contains(a, b) __contains__(a, b) diff --git a/Lib/operator.py b/Lib/operator.py index 02ccdaa13ddb31..2514fc9e4d3a68 100644 --- a/Lib/operator.py +++ b/Lib/operator.py @@ -12,11 +12,11 @@ __all__ = ['abs', 'add', 'and_', 'attrgetter', 'call', 'concat', 'contains', 'countOf', 'delitem', 'eq', 'floordiv', 'ge', 'getitem', 'gt', 'iadd', 'iand', - 'iconcat', 'ifloordiv', 'ilshift', 'imatmul', 'imod', 'imul', + 'iconcat', 'ifloordiv', 'ilshift', 'imatmul', 'imod', 'imul', 'in_', 'index', 'indexOf', 'inv', 'invert', 'ior', 'ipow', 'irshift', 'is_', 'is_not', 'isub', 'itemgetter', 'itruediv', 'ixor', 'le', 'length_hint', 'lshift', 'lt', 'matmul', 'methodcaller', 'mod', - 'mul', 'ne', 'neg', 'not_', 'or_', 'pos', 'pow', 'rshift', + 'mul', 'ne', 'neg', 'not_', 'not_in', 'or_', 'pos', 'pow', 'rshift', 'setitem', 'sub', 'truediv', 'truth', 'xor'] from builtins import abs as _abs @@ -150,6 +150,14 @@ def concat(a, b): raise TypeError(msg) return a + b +def in_(a, b): + "Same as a in b." + return a in b + +def not_in(a, b): + "Same as a not in b." + return a not in b + def contains(a, b): "Same as b in a (note reversed operands)." return b in a diff --git a/Lib/test/test_operator.py b/Lib/test/test_operator.py index f8eac8dc002636..e8cdd0178d5758 100644 --- a/Lib/test/test_operator.py +++ b/Lib/test/test_operator.py @@ -290,6 +290,22 @@ def test_rshift(self): self.assertEqual(operator.rshift(5, 0), 5) self.assertRaises(ValueError, operator.rshift, 2, -1) + def test_in(self): + operator = self.module + self.assertRaises(TypeError, operator.in_) + self.assertRaises(TypeError, operator.in_, None, None) + self.assertRaises(ZeroDivisionError, operator.in_, 1, BadIterable()) + self.assertTrue(operator.in_(2, range(4))) + self.assertFalse(operator.in_(5, range(4))) + + def test_not_in(self): + operator = self.module + self.assertRaises(TypeError, operator.not_in) + self.assertRaises(TypeError, operator.not_in, None, None) + self.assertRaises(ZeroDivisionError, operator.not_in, 1, BadIterable()) + self.assertFalse(operator.not_in(2, range(4))) + self.assertTrue(operator.not_in(5, range(4))) + def test_contains(self): operator = self.module self.assertRaises(TypeError, operator.contains) diff --git a/Misc/NEWS.d/next/Library/2024-06-12-01-12-19.gh-issue-120376.R0lyvC.rst b/Misc/NEWS.d/next/Library/2024-06-12-01-12-19.gh-issue-120376.R0lyvC.rst new file mode 100644 index 00000000000000..4f795b9804a6ba --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-12-01-12-19.gh-issue-120376.R0lyvC.rst @@ -0,0 +1 @@ +Add :func:`operator.in_` and :func:`operator.not_in` to the :mod:`operator` module. diff --git a/Modules/_operator.c b/Modules/_operator.c index 5d3f88327d19ad..0a342e49eadee4 100644 --- a/Modules/_operator.c +++ b/Modules/_operator.c @@ -474,6 +474,44 @@ _operator_iconcat_impl(PyObject *module, PyObject *a, PyObject *b) return PySequence_InPlaceConcat(a, b); } +/*[clinic input] +_operator.in_ -> bool + + a: object + b: object + / + +Same as a in b. +[clinic start generated code]*/ + +static int +_operator_in__impl(PyObject *module, PyObject *a, PyObject *b) +/*[clinic end generated code: output=cd4eca456096fa9a input=8bd20ace23fe9808]*/ +{ + return PySequence_Contains(b, a); +} + +/*[clinic input] +_operator.not_in -> bool + + a: object + b: object + / + +Same as a not in b. +[clinic start generated code]*/ + +static int +_operator_not_in_impl(PyObject *module, PyObject *a, PyObject *b) +/*[clinic end generated code: output=93a4db26908835bd input=203499d558a29ee3]*/ +{ + int result = PySequence_Contains(b, a); + if (result != -1) { + result = !result; + } + return result; +} + /*[clinic input] _operator.contains -> bool @@ -910,6 +948,8 @@ _operator_call(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje static struct PyMethodDef operator_methods[] = { _OPERATOR_TRUTH_METHODDEF + _OPERATOR_IN__METHODDEF + _OPERATOR_NOT_IN_METHODDEF _OPERATOR_CONTAINS_METHODDEF _OPERATOR_INDEXOF_METHODDEF _OPERATOR_COUNTOF_METHODDEF diff --git a/Modules/clinic/_operator.c.h b/Modules/clinic/_operator.c.h index 08615d690922a1..2351fe330a7725 100644 --- a/Modules/clinic/_operator.c.h +++ b/Modules/clinic/_operator.c.h @@ -886,6 +886,76 @@ _operator_iconcat(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return return_value; } +PyDoc_STRVAR(_operator_in___doc__, +"in_($module, a, b, /)\n" +"--\n" +"\n" +"Same as a in b."); + +#define _OPERATOR_IN__METHODDEF \ + {"in_", _PyCFunction_CAST(_operator_in_), METH_FASTCALL, _operator_in___doc__}, + +static int +_operator_in__impl(PyObject *module, PyObject *a, PyObject *b); + +static PyObject * +_operator_in_(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *a; + PyObject *b; + int _return_value; + + if (!_PyArg_CheckPositional("in_", nargs, 2, 2)) { + goto exit; + } + a = args[0]; + b = args[1]; + _return_value = _operator_in__impl(module, a, b); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyBool_FromLong((long)_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(_operator_not_in__doc__, +"not_in($module, a, b, /)\n" +"--\n" +"\n" +"Same as a not in b."); + +#define _OPERATOR_NOT_IN_METHODDEF \ + {"not_in", _PyCFunction_CAST(_operator_not_in), METH_FASTCALL, _operator_not_in__doc__}, + +static int +_operator_not_in_impl(PyObject *module, PyObject *a, PyObject *b); + +static PyObject * +_operator_not_in(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *a; + PyObject *b; + int _return_value; + + if (!_PyArg_CheckPositional("not_in", nargs, 2, 2)) { + goto exit; + } + a = args[0]; + b = args[1]; + _return_value = _operator_not_in_impl(module, a, b); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyBool_FromLong((long)_return_value); + +exit: + return return_value; +} + PyDoc_STRVAR(_operator_contains__doc__, "contains($module, a, b, /)\n" "--\n" @@ -1489,4 +1559,4 @@ _operator__compare_digest(PyObject *module, PyObject *const *args, Py_ssize_t na exit: return return_value; } -/*[clinic end generated code: output=ddbba2cd943571eb input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c240b42c5e1a2d8f input=a9049054013a1b77]*/