Skip to content

Commit a200c39

Browse files
committed
gh-94808: Cover PyFunction_GetKwDefaults and PyFunction_SetKwDefaults
1 parent 5a8c4b9 commit a200c39

File tree

2 files changed

+118
-3
lines changed

2 files changed

+118
-3
lines changed

Lib/test/test_capi.py

+89-3
Original file line numberDiff line numberDiff line change
@@ -943,18 +943,32 @@ def some():
943943
_testcapi.function_get_module(None) # not a function
944944

945945
def test_function_get_defaults(self):
946-
def some(pos_only='p', zero=0, optional=None):
946+
def some(
947+
pos_only1, pos_only2='p',
948+
/,
949+
zero=0, optional=None,
950+
*,
951+
kw1,
952+
kw2=True,
953+
):
947954
pass
948955

949956
defaults = _testcapi.function_get_defaults(some)
950957
self.assertEqual(defaults, ('p', 0, None))
951958
self.assertEqual(defaults, some.__defaults__)
952959

953960
with self.assertRaises(SystemError):
954-
_testcapi.function_get_module(None) # not a function
961+
_testcapi.function_get_defaults(None) # not a function
955962

956963
def test_function_set_defaults(self):
957-
def some(pos_only='p', zero=0, optional=None):
964+
def some(
965+
pos_only1, pos_only2='p',
966+
/,
967+
zero=0, optional=None,
968+
*,
969+
kw1,
970+
kw2=True,
971+
):
958972
pass
959973

960974
old_defaults = ('p', 0, None)
@@ -963,6 +977,8 @@ def some(pos_only='p', zero=0, optional=None):
963977

964978
with self.assertRaises(SystemError):
965979
_testcapi.function_set_defaults(some, 1) # not tuple or None
980+
with self.assertRaises(SystemError):
981+
_testcapi.function_set_defaults(1, ()) # not a function
966982
self.assertEqual(_testcapi.function_get_defaults(some), old_defaults)
967983
self.assertEqual(some.__defaults__, old_defaults)
968984

@@ -971,6 +987,12 @@ def some(pos_only='p', zero=0, optional=None):
971987
self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
972988
self.assertEqual(some.__defaults__, new_defaults)
973989

990+
# Empty tuple is fine:
991+
new_defaults = ()
992+
_testcapi.function_set_defaults(some, new_defaults)
993+
self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
994+
self.assertEqual(some.__defaults__, new_defaults)
995+
974996
class tuplesub(tuple): ... # tuple subclasses must work
975997

976998
new_defaults = tuplesub(((1, 2), ['a', 'b'], None))
@@ -984,6 +1006,70 @@ class tuplesub(tuple): ... # tuple subclasses must work
9841006
self.assertEqual(_testcapi.function_get_defaults(some), None)
9851007
self.assertEqual(some.__defaults__, None)
9861008

1009+
def test_function_get_kw_defaults(self):
1010+
def some(
1011+
pos_only1, pos_only2='p',
1012+
/,
1013+
zero=0, optional=None,
1014+
*,
1015+
kw1,
1016+
kw2=True,
1017+
):
1018+
pass
1019+
1020+
defaults = _testcapi.function_get_kw_defaults(some)
1021+
self.assertEqual(defaults, {'kw2': True})
1022+
self.assertEqual(defaults, some.__kwdefaults__)
1023+
1024+
with self.assertRaises(SystemError):
1025+
_testcapi.function_get_kw_defaults(None) # not a function
1026+
1027+
def test_function_set_kw_defaults(self):
1028+
def some(
1029+
pos_only1, pos_only2='p',
1030+
/,
1031+
zero=0, optional=None,
1032+
*,
1033+
kw1,
1034+
kw2=True,
1035+
):
1036+
pass
1037+
1038+
old_defaults = {'kw2': True}
1039+
self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
1040+
self.assertEqual(some.__kwdefaults__, old_defaults)
1041+
1042+
with self.assertRaises(SystemError):
1043+
_testcapi.function_set_kw_defaults(some, 1) # not dict or None
1044+
with self.assertRaises(SystemError):
1045+
_testcapi.function_set_kw_defaults(1, {}) # not a function
1046+
self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
1047+
self.assertEqual(some.__kwdefaults__, old_defaults)
1048+
1049+
new_defaults = {'kw2': (1, 2, 3)}
1050+
_testcapi.function_set_kw_defaults(some, new_defaults)
1051+
self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
1052+
self.assertEqual(some.__kwdefaults__, new_defaults)
1053+
1054+
# Empty dict is fine:
1055+
new_defaults = {}
1056+
_testcapi.function_set_kw_defaults(some, new_defaults)
1057+
self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
1058+
self.assertEqual(some.__kwdefaults__, new_defaults)
1059+
1060+
class dictsub(dict): ... # dict subclasses must work
1061+
1062+
new_defaults = dictsub({'kw2': None})
1063+
_testcapi.function_set_kw_defaults(some, new_defaults)
1064+
self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
1065+
self.assertEqual(some.__kwdefaults__, new_defaults)
1066+
1067+
# `None` is special, it sets `kwdefaults` to `NULL`,
1068+
# it needs special handling in `_testcapi`:
1069+
_testcapi.function_set_kw_defaults(some, None)
1070+
self.assertEqual(_testcapi.function_get_kw_defaults(some), None)
1071+
self.assertEqual(some.__kwdefaults__, None)
1072+
9871073

9881074
class TestPendingCalls(unittest.TestCase):
9891075

Modules/_testcapimodule.c

+29
Original file line numberDiff line numberDiff line change
@@ -5815,6 +5815,33 @@ function_set_defaults(PyObject *self, PyObject *args)
58155815
Py_RETURN_NONE;
58165816
}
58175817

5818+
static PyObject *
5819+
function_get_kw_defaults(PyObject *self, PyObject *func)
5820+
{
5821+
PyObject *defaults = PyFunction_GetKwDefaults(func);
5822+
if (defaults != NULL) {
5823+
Py_INCREF(defaults);
5824+
return defaults;
5825+
} else if (PyErr_Occurred()) {
5826+
return NULL;
5827+
} else {
5828+
Py_RETURN_NONE; // This can happen when `kwdefaults` are set to `None`
5829+
}
5830+
}
5831+
5832+
static PyObject *
5833+
function_set_kw_defaults(PyObject *self, PyObject *args)
5834+
{
5835+
PyObject *func = NULL, *defaults = NULL;
5836+
if (!PyArg_ParseTuple(args, "OO", &func, &defaults)) {
5837+
return NULL;
5838+
}
5839+
int result = PyFunction_SetKwDefaults(func, defaults);
5840+
if (result == -1)
5841+
return NULL;
5842+
Py_RETURN_NONE;
5843+
}
5844+
58185845

58195846
// type watchers
58205847

@@ -6231,6 +6258,8 @@ static PyMethodDef TestMethods[] = {
62316258
{"function_get_module", function_get_module, METH_O, NULL},
62326259
{"function_get_defaults", function_get_defaults, METH_O, NULL},
62336260
{"function_set_defaults", function_set_defaults, METH_VARARGS, NULL},
6261+
{"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL},
6262+
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
62346263
{"add_type_watcher", add_type_watcher, METH_O, NULL},
62356264
{"clear_type_watcher", clear_type_watcher, METH_O, NULL},
62366265
{"watch_type", watch_type, METH_VARARGS, NULL},

0 commit comments

Comments
 (0)