Skip to content

Commit 82837ae

Browse files
cmaloneyclaude
andcommitted
gh-87613: Pass NULL for an empty **kwargs in Argument Clinic
The parser allocated an empty dict for **kwargs when there were no keyword arguments. Pass NULL instead, like the caller does, and handle NULL in the impls that used the dict unconditionally. All **kwargs impls were audited: _csv and zoneinfo already accept NULL, and the rest (structseq and SimpleNamespace `__replace__`, _interpreters.new_config, atexit.register, SubElement) now check for it. atexit stores None, which the run loop already maps back to NULL. pyperf, PGO+LTO, `t = time.gmtime(0)`, `ns = SimpleNamespace(a=1)`: | Benchmark | main | this change | |---------------------------------|:------:|:---------------------:| | struct_time.`__replace__`() | 229 ns | 47.0 ns: 4.87x faster | | SimpleNamespace.`__replace__`() | 72.2 ns | 65.5 ns: 1.10x faster | Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent aaaf61c commit 82837ae

15 files changed

Lines changed: 45 additions & 172 deletions

‎Lib/test/test_clinic.py‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5708,7 +5708,7 @@ def errmsg(name):
57085708
def test_lone_kwds(self):
57095709
with self.assertRaises(TypeError):
57105710
ac_tester.lone_kwds(1, 2)
5711-
self.assertEqual(ac_tester.lone_kwds(), ({},))
5711+
self.assertEqual(ac_tester.lone_kwds(), (None,))
57125712
self.assertEqual(ac_tester.lone_kwds(y='y'), ({'y': 'y'},))
57135713
kwds = {'y': 'y', 'z': 'z'}
57145714
self.assertEqual(ac_tester.lone_kwds(y='y', z='z'), (kwds,))
@@ -5721,7 +5721,7 @@ def test_kwds_with_pos_only(self):
57215721
ac_tester.kwds_with_pos_only(y='y')
57225722
with self.assertRaises(TypeError):
57235723
ac_tester.kwds_with_pos_only(1, y='y')
5724-
self.assertEqual(ac_tester.kwds_with_pos_only(1, 2), (1, 2, {}))
5724+
self.assertEqual(ac_tester.kwds_with_pos_only(1, 2), (1, 2, None))
57255725
self.assertEqual(ac_tester.kwds_with_pos_only(1, 2, y='y'), (1, 2, {'y': 'y'}))
57265726
kwds = {'y': 'y', 'z': 'z'}
57275727
self.assertEqual(ac_tester.kwds_with_pos_only(1, 2, y='y', z='z'), (1, 2, kwds))
@@ -5732,16 +5732,16 @@ def test_kwds_with_optional_pos_only(self):
57325732
ac_tester.kwds_with_optional_pos_only()
57335733
with self.assertRaises(TypeError):
57345734
ac_tester.kwds_with_optional_pos_only(y='y')
5735-
self.assertEqual(ac_tester.kwds_with_optional_pos_only(1), (1, None, {}))
5736-
self.assertEqual(ac_tester.kwds_with_optional_pos_only(1, 2), (1, 2, {}))
5735+
self.assertEqual(ac_tester.kwds_with_optional_pos_only(1), (1, None, None))
5736+
self.assertEqual(ac_tester.kwds_with_optional_pos_only(1, 2), (1, 2, None))
57375737
self.assertEqual(ac_tester.kwds_with_optional_pos_only(1, y='y'),
57385738
(1, None, {'y': 'y'}))
57395739
self.assertEqual(ac_tester.kwds_with_optional_pos_only(1, 2, y='y'),
57405740
(1, 2, {'y': 'y'}))
57415741

57425742
def test_kwds_with_stararg(self):
5743-
self.assertEqual(ac_tester.kwds_with_stararg(), ((), {}))
5744-
self.assertEqual(ac_tester.kwds_with_stararg(1, 2), ((1, 2), {}))
5743+
self.assertEqual(ac_tester.kwds_with_stararg(), ((), None))
5744+
self.assertEqual(ac_tester.kwds_with_stararg(1, 2), ((1, 2), None))
57455745
self.assertEqual(ac_tester.kwds_with_stararg(y='y'), ((), {'y': 'y'}))
57465746
args = (1, 2)
57475747
kwds = {'y': 'y', 'z': 'z'}
@@ -5753,7 +5753,7 @@ def test_kwds_with_pos_only_and_stararg(self):
57535753
ac_tester.kwds_with_pos_only_and_stararg()
57545754
with self.assertRaises(TypeError):
57555755
ac_tester.kwds_with_pos_only_and_stararg(y='y')
5756-
self.assertEqual(ac_tester.kwds_with_pos_only_and_stararg(1, 2), (1, 2, (), {}))
5756+
self.assertEqual(ac_tester.kwds_with_pos_only_and_stararg(1, 2), (1, 2, (), None))
57575757
self.assertEqual(ac_tester.kwds_with_pos_only_and_stararg(1, 2, y='y'), (1, 2, (), {'y': 'y'}))
57585758
args = ('lobster', 'thermidor')
57595759
kwds = {'y': 'y', 'z': 'z'}

‎Modules/_elementtree.c‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -640,11 +640,11 @@ _elementtree_SubElement_impl(PyObject *module, PyObject *parent,
640640
attrib = PyDict_Copy(attrib);
641641
if (!attrib)
642642
return NULL;
643-
if (PyDict_Update(attrib, extra) < 0) {
643+
if (extra && PyDict_Update(attrib, extra) < 0) {
644644
Py_DECREF(attrib);
645645
return NULL;
646646
}
647-
} else if (PyDict_GET_SIZE(extra)) {
647+
} else if (extra && PyDict_GET_SIZE(extra)) {
648648
/* have keyword args */
649649
attrib = get_attrib_from_keywords(extra);
650650
if (!attrib)

‎Modules/_interpretersmodule.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -828,7 +828,7 @@ _interpreters_new_config_impl(PyObject *module, const char *name,
828828
return NULL;
829829
}
830830

831-
if (PyDict_GET_SIZE(overrides) > 0) {
831+
if (overrides != NULL && PyDict_GET_SIZE(overrides) > 0) {
832832
if (_PyInterpreterConfig_UpdateFromDict(&config, overrides) < 0) {
833833
return NULL;
834834
}

‎Modules/atexitmodule.c‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,8 @@ atexit_register_impl(PyObject *module, PyObject *func, PyObject *args,
191191
"the first argument must be callable");
192192
return NULL;
193193
}
194-
PyObject *callback = PyTuple_Pack(3, func, args, kwargs);
194+
PyObject *callback = PyTuple_Pack(3, func, args,
195+
kwargs ? kwargs : Py_None);
195196
if (callback == NULL)
196197
{
197198
return NULL;

‎Modules/clinic/_csv.c.h‎

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

‎Modules/clinic/_elementtree.c.h‎

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

‎Modules/clinic/_interpretersmodule.c.h‎

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

‎Modules/clinic/_testclinic_kwds.c.h‎

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

‎Modules/clinic/_zoneinfo.c.h‎

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

‎Modules/clinic/atexitmodule.c.h‎

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

0 commit comments

Comments
 (0)