From e5e927938bea9de5f15a310c765f19c224a49542 Mon Sep 17 00:00:00 2001 From: furkanonder Date: Fri, 26 May 2023 00:19:02 +0300 Subject: [PATCH 1/2] make function __closure__ writable --- Doc/reference/datamodel.rst | 2 +- Lib/test/test_funcattrs.py | 28 ++++++++++++++++-- Objects/funcobject.c | 57 ++++++++++++++++++++++++++++++++++++- 3 files changed, 82 insertions(+), 5 deletions(-) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index c0734e49f29192..c3072f0a042e8a 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -541,7 +541,7 @@ Callable types | | arbitrary function | | | | attributes. | | +-------------------------+-------------------------------+-----------+ - | :attr:`__closure__` | ``None`` or a tuple of cells | Read-only | + | :attr:`__closure__` | ``None`` or a tuple of cells | Writable | | | that contain bindings for the | | | | function's free variables. | | | | See below for information on | | diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py index e08d72877d8aef..73f6f0aceae0ac 100644 --- a/Lib/test/test_funcattrs.py +++ b/Lib/test/test_funcattrs.py @@ -108,13 +108,35 @@ def func3(s): pass def test___closure__(self): a = 12 - def f(): print(a) + b = 67 + def f(): + return a + b c = f.__closure__ self.assertIsInstance(c, tuple) - self.assertEqual(len(c), 1) + self.assertEqual(len(c), 2) # don't have a type object handy self.assertEqual(c[0].__class__.__name__, "cell") - self.cannot_set_attr(f, "__closure__", c, AttributeError) + + # check that we can overwrite the __closure__ + self.assertEqual(f(), 79) + good_closure = f.__closure__ = (cell(42), cell(34)) + self.assertEqual(f(), 76) + + # check that we can't write anything besides a 2-tuple of cells or None + with self.assertRaises(TypeError): + f.__closure__ = 'spam' # wrong type + with self.assertRaises(ValueError): + f.__closure__ = () # too short + with self.assertRaises(ValueError): + f.__closure__ = (cell(1),) # too short + with self.assertRaises(ValueError): + f.__closure__ = (cell(1), cell(2), cell(3)) # too long + with self.assertRaises(TypeError): + f.__closure__ = (cell(1), 2) # wrong element type + + # __closure__ should not be affected by the previous exceptions + self.assertIs(f.__closure__, good_closure) + self.assertEqual(f(), 76) def test_cell_new(self): cell_obj = types.CellType(1) diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 753038600aa858..4f6c820393d0b0 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -449,7 +449,6 @@ PyFunction_SetAnnotations(PyObject *op, PyObject *annotations) #define OFF(x) offsetof(PyFunctionObject, x) static PyMemberDef func_memberlist[] = { - {"__closure__", T_OBJECT, OFF(func_closure), READONLY}, {"__doc__", T_OBJECT, OFF(func_doc), 0}, {"__globals__", T_OBJECT, OFF(func_globals), READONLY}, {"__module__", T_OBJECT, OFF(func_module), 0}, @@ -690,8 +689,64 @@ _Py_set_function_type_params(PyThreadState *Py_UNUSED(ignored), PyObject *func, return Py_NewRef(func); } +static PyObject * +func_get_closure(PyFunctionObject *op, void *Py_UNUSED(ignored)) +{ + if (op->func_closure == NULL) { + Py_INCREF(Py_None); + return Py_None; + } + Py_INCREF(op->func_closure); + return op->func_closure; +} + +static int +func_set_closure(PyFunctionObject *op, PyObject *value, void *Py_UNUSED(ignored)) +{ + PyObject *tmp; + Py_ssize_t nfree, nclosure; + PyCodeObject *code; + + if (value != Py_None && !PyTuple_Check(value)) { + PyErr_SetString(PyExc_TypeError, + "__closure__ must be set to a tuple object or None"); + return -1; + } + + code = (PyCodeObject*)op->func_code; + nfree = PyTuple_GET_SIZE(PyCode_GetFreevars(code)); + nclosure = value == Py_None ? 0 : PyTuple_GET_SIZE(value); + + if (nfree != nclosure) { + PyErr_Format(PyExc_ValueError, + "%U requires closure of length %zd, not %zd", + code->co_name, nfree, nclosure); + return -1; + } + + if (nclosure) { + Py_ssize_t i; + for (i = 0; i < nclosure; i++) { + PyObject *o = PyTuple_GET_ITEM(value, i); + if (!PyCell_Check(o)) { + PyErr_Format(PyExc_TypeError, + "expected only cells in closure, found %s", + o->ob_type->tp_name); + return -1; + } + } + } + + tmp = op->func_closure; + Py_XINCREF(value); + op->func_closure = value; + Py_XDECREF(tmp); + return 0; +} + static PyGetSetDef func_getsetlist[] = { {"__code__", (getter)func_get_code, (setter)func_set_code}, + {"__closure__", (getter)func_get_closure, (setter)func_set_closure}, {"__defaults__", (getter)func_get_defaults, (setter)func_set_defaults}, {"__kwdefaults__", (getter)func_get_kwdefaults, From 453dc702d424c3f2c5438283ec4f18f855da1ed0 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 25 May 2023 21:38:04 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2023-05-25-21-37-59.gh-issue-58577.kgMeib.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-05-25-21-37-59.gh-issue-58577.kgMeib.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-05-25-21-37-59.gh-issue-58577.kgMeib.rst b/Misc/NEWS.d/next/Core and Builtins/2023-05-25-21-37-59.gh-issue-58577.kgMeib.rst new file mode 100644 index 00000000000000..8d793409429d34 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-05-25-21-37-59.gh-issue-58577.kgMeib.rst @@ -0,0 +1 @@ +Make function :attr:`__closure__` writable. Patched by Richard Oudkerk and Furkan Onder.