diff -r 08d4c2fe51ea Doc/reference/datamodel.rst --- a/Doc/reference/datamodel.rst Tue Apr 24 22:56:57 2012 +0200 +++ b/Doc/reference/datamodel.rst Wed Apr 25 15:33:19 2012 +0100 @@ -477,7 +477,7 @@ | | 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 -r 08d4c2fe51ea Lib/test/test_funcattrs.py --- a/Lib/test/test_funcattrs.py Tue Apr 24 22:56:57 2012 +0200 +++ b/Lib/test/test_funcattrs.py Wed Apr 25 15:33:19 2012 +0100 @@ -71,13 +71,58 @@ 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 + with self.assertRaises(TypeError): + f.__closure__ = 'spam' # wrong type + with self.assertRaises(ValueError): + f.__closure__ = None # too short + 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 g(): + return 123 + + # check we can't write anything besides None or () - + # type.FunctionType allows either for code object with + # trivial closure + g.__closure__ = None + self.assertEqual(g(), 123) + self.assertEqual(g.__closure__, None) + + g.__closure__ = () + self.assertEqual(g(), 123) + self.assertEqual(g.__closure__, ()) + + with self.assertRaises(ValueError): + g.__closure__ = (cell(1), cell(2)) + + with self.assertRaises(TypeError): + g.__closure__ = "spam" def test_empty_cell(self): def f(): print(a) diff -r 08d4c2fe51ea Objects/funcobject.c --- a/Objects/funcobject.c Tue Apr 24 22:56:57 2012 +0200 +++ b/Objects/funcobject.c Wed Apr 25 15:33:19 2012 +0100 @@ -235,8 +235,6 @@ #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,60 @@ } static PyObject * +func_get_closure(PyFunctionObject *op) +{ + if (op->func_closure == NULL) { + Py_RETURN_NONE; + } + Py_INCREF(op->func_closure); + return op->func_closure; +} + +static int +func_set_closure(PyFunctionObject *op, PyObject *value) +{ + 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(code->co_freevars); + 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 PyObject * func_get_kwdefaults(PyFunctionObject *op) { if (op->func_kwdefaults == NULL) { @@ -434,6 +486,7 @@ 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,