diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 1f1a660..3de832f 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -477,7 +477,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. | | +-------------------------+-------------------------------+-----------+ diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py index c8ed830..bff28fe 100644 --- a/Lib/test/test_funcattrs.py +++ b/Lib/test/test_funcattrs.py @@ -71,13 +71,28 @@ class FunctionPropertiesTest(FuncAttrsTest): def test___closure__(self): a = 12 - def f(): print(a) + def f(): return a c = f.__closure__ self.assertIsInstance(c, tuple) self.assertEqual(len(c), 1) # 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(), 12) + f.__closure__ = (cell(42),) + self.assertEqual(f(), 42) + # check that we can't write anything besides None and tuples + with self.assertRaises(TypeError): + f.__closure__ = 'spam' + # __closure__ should not be affected by the previous exception + self.assertEqual(f(), 42) + # reset to None + f.__closure__ = None + self.assertIs(f.__closure__, None) + # and set back to some value + f.__closure__ = (cell(100),) + # check that the function is still OK + self.assertEqual(f(), 100) def test_empty_cell(self): def f(): print(a) diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 49415b9..a6f6a8b 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -235,8 +235,6 @@ PyFunction_SetAnnotations(PyObject *op, PyObject *annotations) #define OFF(x) offsetof(PyFunctionObject, x) static PyMemberDef func_memberlist[] = { - {"__closure__", T_OBJECT, OFF(func_closure), - RESTRICTED|READONLY}, {"__doc__", T_OBJECT, OFF(func_doc), PY_WRITE_RESTRICTED}, {"__globals__", T_OBJECT, OFF(func_globals), RESTRICTED|READONLY}, @@ -367,6 +365,37 @@ func_set_defaults(PyFunctionObject *op, PyObject *value) } static PyObject * +func_get_closure(PyFunctionObject *op) +{ + 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) +{ + PyObject *tmp; + + /* Can only set func_closure to NULL or a tuple. */ + if (value == Py_None) + value = NULL; + else if (!PyTuple_Check(value)) { + PyErr_SetString(PyExc_TypeError, + "__closure__ must be set to a tuple object"); + return -1; + } + tmp = op->func_closure; + Py_XINCREF(value); + op->func_closure = value; + Py_XDECREF(tmp); + return 0; +} + +static PyObject * func_get_kwdefaults(PyFunctionObject *op) { if (op->func_kwdefaults == NULL) { @@ -434,6 +463,7 @@ func_set_annotations(PyFunctionObject *op, PyObject *value) 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,