Skip to content

Commit 317acb8

Browse files
authored
gh-94808: add tests covering PyFunction_GetKwDefaults and PyFunction_SetKwDefaults (GH-98809)
1 parent c5c4077 commit 317acb8

File tree

2 files changed

+124
-3
lines changed

2 files changed

+124
-3
lines changed

Lib/test/test_capi.py

+95-3
Original file line numberDiff line numberDiff line change
@@ -1038,18 +1038,32 @@ def some():
10381038
_testcapi.function_get_module(None) # not a function
10391039

10401040
def test_function_get_defaults(self):
1041-
def some(pos_only='p', zero=0, optional=None):
1041+
def some(
1042+
pos_only1, pos_only2='p',
1043+
/,
1044+
zero=0, optional=None,
1045+
*,
1046+
kw1,
1047+
kw2=True,
1048+
):
10421049
pass
10431050

10441051
defaults = _testcapi.function_get_defaults(some)
10451052
self.assertEqual(defaults, ('p', 0, None))
10461053
self.assertEqual(defaults, some.__defaults__)
10471054

10481055
with self.assertRaises(SystemError):
1049-
_testcapi.function_get_module(None) # not a function
1056+
_testcapi.function_get_defaults(None) # not a function
10501057

10511058
def test_function_set_defaults(self):
1052-
def some(pos_only='p', zero=0, optional=None):
1059+
def some(
1060+
pos_only1, pos_only2='p',
1061+
/,
1062+
zero=0, optional=None,
1063+
*,
1064+
kw1,
1065+
kw2=True,
1066+
):
10531067
pass
10541068

10551069
old_defaults = ('p', 0, None)
@@ -1061,11 +1075,22 @@ def some(pos_only='p', zero=0, optional=None):
10611075
self.assertEqual(_testcapi.function_get_defaults(some), old_defaults)
10621076
self.assertEqual(some.__defaults__, old_defaults)
10631077

1078+
with self.assertRaises(SystemError):
1079+
_testcapi.function_set_defaults(1, ()) # not a function
1080+
self.assertEqual(_testcapi.function_get_defaults(some), old_defaults)
1081+
self.assertEqual(some.__defaults__, old_defaults)
1082+
10641083
new_defaults = ('q', 1, None)
10651084
_testcapi.function_set_defaults(some, new_defaults)
10661085
self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
10671086
self.assertEqual(some.__defaults__, new_defaults)
10681087

1088+
# Empty tuple is fine:
1089+
new_defaults = ()
1090+
_testcapi.function_set_defaults(some, new_defaults)
1091+
self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
1092+
self.assertEqual(some.__defaults__, new_defaults)
1093+
10691094
class tuplesub(tuple): ... # tuple subclasses must work
10701095

10711096
new_defaults = tuplesub(((1, 2), ['a', 'b'], None))
@@ -1079,6 +1104,73 @@ class tuplesub(tuple): ... # tuple subclasses must work
10791104
self.assertEqual(_testcapi.function_get_defaults(some), None)
10801105
self.assertEqual(some.__defaults__, None)
10811106

1107+
def test_function_get_kw_defaults(self):
1108+
def some(
1109+
pos_only1, pos_only2='p',
1110+
/,
1111+
zero=0, optional=None,
1112+
*,
1113+
kw1,
1114+
kw2=True,
1115+
):
1116+
pass
1117+
1118+
defaults = _testcapi.function_get_kw_defaults(some)
1119+
self.assertEqual(defaults, {'kw2': True})
1120+
self.assertEqual(defaults, some.__kwdefaults__)
1121+
1122+
with self.assertRaises(SystemError):
1123+
_testcapi.function_get_kw_defaults(None) # not a function
1124+
1125+
def test_function_set_kw_defaults(self):
1126+
def some(
1127+
pos_only1, pos_only2='p',
1128+
/,
1129+
zero=0, optional=None,
1130+
*,
1131+
kw1,
1132+
kw2=True,
1133+
):
1134+
pass
1135+
1136+
old_defaults = {'kw2': True}
1137+
self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
1138+
self.assertEqual(some.__kwdefaults__, old_defaults)
1139+
1140+
with self.assertRaises(SystemError):
1141+
_testcapi.function_set_kw_defaults(some, 1) # not dict or None
1142+
self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
1143+
self.assertEqual(some.__kwdefaults__, old_defaults)
1144+
1145+
with self.assertRaises(SystemError):
1146+
_testcapi.function_set_kw_defaults(1, {}) # not a function
1147+
self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
1148+
self.assertEqual(some.__kwdefaults__, old_defaults)
1149+
1150+
new_defaults = {'kw2': (1, 2, 3)}
1151+
_testcapi.function_set_kw_defaults(some, new_defaults)
1152+
self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
1153+
self.assertEqual(some.__kwdefaults__, new_defaults)
1154+
1155+
# Empty dict is fine:
1156+
new_defaults = {}
1157+
_testcapi.function_set_kw_defaults(some, new_defaults)
1158+
self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
1159+
self.assertEqual(some.__kwdefaults__, new_defaults)
1160+
1161+
class dictsub(dict): ... # dict subclasses must work
1162+
1163+
new_defaults = dictsub({'kw2': None})
1164+
_testcapi.function_set_kw_defaults(some, new_defaults)
1165+
self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
1166+
self.assertEqual(some.__kwdefaults__, new_defaults)
1167+
1168+
# `None` is special, it sets `kwdefaults` to `NULL`,
1169+
# it needs special handling in `_testcapi`:
1170+
_testcapi.function_set_kw_defaults(some, None)
1171+
self.assertEqual(_testcapi.function_get_kw_defaults(some), None)
1172+
self.assertEqual(some.__kwdefaults__, None)
1173+
10821174

10831175
class TestPendingCalls(unittest.TestCase):
10841176

Modules/_testcapimodule.c

+29
Original file line numberDiff line numberDiff line change
@@ -5862,6 +5862,33 @@ function_set_defaults(PyObject *self, PyObject *args)
58625862
Py_RETURN_NONE;
58635863
}
58645864

5865+
static PyObject *
5866+
function_get_kw_defaults(PyObject *self, PyObject *func)
5867+
{
5868+
PyObject *defaults = PyFunction_GetKwDefaults(func);
5869+
if (defaults != NULL) {
5870+
Py_INCREF(defaults);
5871+
return defaults;
5872+
} else if (PyErr_Occurred()) {
5873+
return NULL;
5874+
} else {
5875+
Py_RETURN_NONE; // This can happen when `kwdefaults` are set to `None`
5876+
}
5877+
}
5878+
5879+
static PyObject *
5880+
function_set_kw_defaults(PyObject *self, PyObject *args)
5881+
{
5882+
PyObject *func = NULL, *defaults = NULL;
5883+
if (!PyArg_ParseTuple(args, "OO", &func, &defaults)) {
5884+
return NULL;
5885+
}
5886+
int result = PyFunction_SetKwDefaults(func, defaults);
5887+
if (result == -1)
5888+
return NULL;
5889+
Py_RETURN_NONE;
5890+
}
5891+
58655892

58665893
// type watchers
58675894

@@ -6281,6 +6308,8 @@ static PyMethodDef TestMethods[] = {
62816308
{"function_get_module", function_get_module, METH_O, NULL},
62826309
{"function_get_defaults", function_get_defaults, METH_O, NULL},
62836310
{"function_set_defaults", function_set_defaults, METH_VARARGS, NULL},
6311+
{"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL},
6312+
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
62846313
{"add_type_watcher", add_type_watcher, METH_O, NULL},
62856314
{"clear_type_watcher", clear_type_watcher, METH_O, NULL},
62866315
{"watch_type", watch_type, METH_VARARGS, NULL},

0 commit comments

Comments
 (0)