Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 | |
Expand Down
28 changes: 25 additions & 3 deletions Lib/test/test_funcattrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Make function :attr:`__closure__` writable. Patched by Richard Oudkerk and Furkan Onder.
57 changes: 56 additions & 1 deletion Objects/funcobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Py_None is immortal now, but I don't know what the policy is about its use in Objects/.

I think that people still use it.

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,
Expand Down