Skip to content

Commit 96ebb20

Browse files
pythongh-155051: Reject None keyword arguments in decimal.localcontext() (pythonGH-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) <noreply@anthropic.com>
1 parent 3f4ed1d commit 96ebb20

4 files changed

Lines changed: 32 additions & 22 deletions

File tree

Lib/test/test_decimal.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3798,6 +3798,13 @@ def test_localcontext_kwargs(self):
37983798
self.assertRaises(TypeError, self.decimal.localcontext, Emin="")
37993799
self.assertRaises(TypeError, self.decimal.localcontext, Emax="")
38003800

3801+
# None is not a valid value for any of these attributes.
3802+
for name in ('prec', 'rounding', 'Emin', 'Emax', 'capitals', 'clamp',
3803+
'flags', 'traps'):
3804+
with self.subTest(name=name):
3805+
self.assertRaises(TypeError, self.decimal.localcontext,
3806+
**{name: None})
3807+
38013808
def test_local_context_kwargs_does_not_overwrite_existing_argument(self):
38023809
ctx = self.decimal.getcontext()
38033810
orig_prec = ctx.prec
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:func:`decimal.localcontext` now raises :exc:`TypeError` if a keyword argument
2+
is ``None``, as the pure Python implementation already did. Previously the C
3+
implementation silently ignored it.

Modules/_decimal/_decimal.c

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,7 +1333,7 @@ context_setattr(PyObject *self, PyObject *name, PyObject *value)
13331333
return PyObject_GenericSetAttr(self, name, value);
13341334
}
13351335

1336-
/* In the constructor and in localcontext() None means "not specified". */
1336+
/* In the constructor None means "not specified". */
13371337
#define NONE_TO_NULL(x) ((x) == Py_None ? NULL : (x))
13381338

13391339
/* Set the given attributes. An attribute is left unchanged if the
@@ -2099,14 +2099,14 @@ _decimal.localcontext
20992099
21002100
ctx as local: object = None
21012101
*
2102-
prec: object = None
2103-
rounding: object = None
2104-
Emin: object = None
2105-
Emax: object = None
2106-
capitals: object = None
2107-
clamp: object = None
2108-
flags: object = None
2109-
traps: object = None
2102+
prec: object = NULL
2103+
rounding: object = NULL
2104+
Emin: object = NULL
2105+
Emax: object = NULL
2106+
capitals: object = NULL
2107+
clamp: object = NULL
2108+
flags: object = NULL
2109+
traps: object = NULL
21102110
21112111
Return a context manager for a copy of the supplied context.
21122112
@@ -2121,7 +2121,7 @@ _decimal_localcontext_impl(PyObject *module, PyObject *local, PyObject *prec,
21212121
PyObject *rounding, PyObject *Emin,
21222122
PyObject *Emax, PyObject *capitals,
21232123
PyObject *clamp, PyObject *flags, PyObject *traps)
2124-
/*[clinic end generated code: output=9bf4e47742a809b0 input=490307b9689c3856]*/
2124+
/*[clinic end generated code: output=9bf4e47742a809b0 input=616abb6ee1654373]*/
21252125
{
21262126
PyObject *global;
21272127

@@ -2142,9 +2142,9 @@ _decimal_localcontext_impl(PyObject *module, PyObject *local, PyObject *prec,
21422142
}
21432143

21442144
int ret = context_setattrs(
2145-
local_copy, NONE_TO_NULL(prec), NONE_TO_NULL(rounding),
2146-
NONE_TO_NULL(Emin), NONE_TO_NULL(Emax), NONE_TO_NULL(capitals),
2147-
NONE_TO_NULL(clamp), NONE_TO_NULL(flags), NONE_TO_NULL(traps)
2145+
local_copy, prec, rounding,
2146+
Emin, Emax, capitals,
2147+
clamp, flags, traps
21482148
);
21492149
if (ret < 0) {
21502150
Py_DECREF(local_copy);

Modules/_decimal/clinic/_decimal.c.h

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)