From: Serhiy Storchaka Date: Sun, 2 Aug 2026 07:08:59 +0000 (+0300) Subject: gh-155051: Reject None keyword arguments in decimal.localcontext() (GH-155054) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=96ebb20fc2f0542d9387091e49626f9a1132de82;p=thirdparty%2FPython%2Fcpython.git gh-155051: Reject None keyword arguments in decimal.localcontext() (GH-155054) None is not a valid value for any of the context attributes. The C implementation silently ignored it, because it shared the code with the Context constructor, where None means "not specified". The pure Python implementation always rejected it. Co-authored-by: Claude Opus 5 (1M context) --- diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 493732c22e82..b8c09c7f43e3 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -3798,6 +3798,13 @@ class ContextWithStatement: self.assertRaises(TypeError, self.decimal.localcontext, Emin="") self.assertRaises(TypeError, self.decimal.localcontext, Emax="") + # None is not a valid value for any of these attributes. + for name in ('prec', 'rounding', 'Emin', 'Emax', 'capitals', 'clamp', + 'flags', 'traps'): + with self.subTest(name=name): + self.assertRaises(TypeError, self.decimal.localcontext, + **{name: None}) + def test_local_context_kwargs_does_not_overwrite_existing_argument(self): ctx = self.decimal.getcontext() orig_prec = ctx.prec diff --git a/Misc/NEWS.d/next/Library/2026-08-01-21-00-00.gh-issue-155051.Lc1Non.rst b/Misc/NEWS.d/next/Library/2026-08-01-21-00-00.gh-issue-155051.Lc1Non.rst new file mode 100644 index 000000000000..d2be40f243d2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-01-21-00-00.gh-issue-155051.Lc1Non.rst @@ -0,0 +1,3 @@ +:func:`decimal.localcontext` now raises :exc:`TypeError` if a keyword argument +is ``None``, as the pure Python implementation already did. Previously the C +implementation silently ignored it. diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 38944e6a9d90..1244505db7c6 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -1333,7 +1333,7 @@ context_setattr(PyObject *self, PyObject *name, PyObject *value) return PyObject_GenericSetAttr(self, name, value); } -/* In the constructor and in localcontext() None means "not specified". */ +/* In the constructor None means "not specified". */ #define NONE_TO_NULL(x) ((x) == Py_None ? NULL : (x)) /* Set the given attributes. An attribute is left unchanged if the @@ -2099,14 +2099,14 @@ _decimal.localcontext ctx as local: object = None * - prec: object = None - rounding: object = None - Emin: object = None - Emax: object = None - capitals: object = None - clamp: object = None - flags: object = None - traps: object = None + prec: object = NULL + rounding: object = NULL + Emin: object = NULL + Emax: object = NULL + capitals: object = NULL + clamp: object = NULL + flags: object = NULL + traps: object = NULL Return a context manager for a copy of the supplied context. @@ -2121,7 +2121,7 @@ _decimal_localcontext_impl(PyObject *module, PyObject *local, PyObject *prec, PyObject *rounding, PyObject *Emin, PyObject *Emax, PyObject *capitals, PyObject *clamp, PyObject *flags, PyObject *traps) -/*[clinic end generated code: output=9bf4e47742a809b0 input=490307b9689c3856]*/ +/*[clinic end generated code: output=9bf4e47742a809b0 input=616abb6ee1654373]*/ { PyObject *global; @@ -2142,9 +2142,9 @@ _decimal_localcontext_impl(PyObject *module, PyObject *local, PyObject *prec, } int ret = context_setattrs( - local_copy, NONE_TO_NULL(prec), NONE_TO_NULL(rounding), - NONE_TO_NULL(Emin), NONE_TO_NULL(Emax), NONE_TO_NULL(capitals), - NONE_TO_NULL(clamp), NONE_TO_NULL(flags), NONE_TO_NULL(traps) + local_copy, prec, rounding, + Emin, Emax, capitals, + clamp, flags, traps ); if (ret < 0) { Py_DECREF(local_copy); diff --git a/Modules/_decimal/clinic/_decimal.c.h b/Modules/_decimal/clinic/_decimal.c.h index 7c45bc66569b..965e5f8d0da9 100644 --- a/Modules/_decimal/clinic/_decimal.c.h +++ b/Modules/_decimal/clinic/_decimal.c.h @@ -628,14 +628,14 @@ _decimal_localcontext(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *argsbuf[9]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; PyObject *local = Py_None; - PyObject *prec = Py_None; - PyObject *rounding = Py_None; - PyObject *Emin = Py_None; - PyObject *Emax = Py_None; - PyObject *capitals = Py_None; - PyObject *clamp = Py_None; - PyObject *flags = Py_None; - PyObject *traps = Py_None; + PyObject *prec = NULL; + PyObject *rounding = NULL; + PyObject *Emin = NULL; + PyObject *Emax = NULL; + PyObject *capitals = NULL; + PyObject *clamp = NULL; + PyObject *flags = NULL; + PyObject *traps = NULL; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, /*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); @@ -7100,4 +7100,4 @@ exit: #ifndef _DECIMAL_CONTEXT_APPLY_METHODDEF #define _DECIMAL_CONTEXT_APPLY_METHODDEF #endif /* !defined(_DECIMAL_CONTEXT_APPLY_METHODDEF) */ -/*[clinic end generated code: output=75ab23467fa0eee3 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=07ec694c7fd6b048 input=a9049054013a1b77]*/