Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 @@ -507,7 +507,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 |

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.

The __closure__ attribute is not writable. The undocumented cell_contents attribute of __closure__ elements is writable.

| | that contain bindings for the | |
| | function's free variables. | |
+-------------------------+-------------------------------+-----------+
Expand Down
14 changes: 14 additions & 0 deletions Lib/test/test_funcattrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,20 @@ def f(): print(a)
self.fail("shouldn't be able to read an empty cell")
a = 12

def test_set_cell(self):
a = 12
def f(): print(a)

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.

If you use def f(): return a instead, you could verify that calling f() does return the updated value.

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.

It's not a blocker to getting this accepted, btw, just nice to have ;-)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That is a good idea!

c = f.__closure__
c[0].cell_contents = 9
self.assertEqual(c[0].cell_contents, 9)

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.

It would be nice to check how setting and deleting cell_contents affects the local variable a.

c[0].cell_contents = 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.

None is a legal value for the cell value.

You should test

del c[0].cell_contents

try:

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.

I understand that you have copied an example from the above test, but this is too verbose. with self.assertRaises(ValueError): is much more compact.

c[0].cell_contents
except ValueError:
pass
else:
self.fail("shouldn't be able to read an empty cell")

def test___name__(self):
self.assertEqual(self.b.__name__, 'b')
self.b.__name__ = 'c'
Expand Down
2 changes: 2 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ Core and Builtins

- bpo-29546: Improve from-import error message with location

- bpo-30486: Allows setting cell values for __closure__. Patch by Lisa Roach.

- Issue #29319: Prevent RunMainFromImporter overwriting sys.path[0].

- Issue #29337: Fixed possible BytesWarning when compare the code objects.
Expand Down
13 changes: 12 additions & 1 deletion Objects/cellobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,19 @@ cell_get_contents(PyCellObject *op, void *closure)
return op->ob_ref;
}

int
cell_set_contents(PyCellObject *op, PyObject *obj)
{
if (obj == 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.

None is a legal value for the cell value.

obj = NULL;
Py_XINCREF(obj);
Py_XSETREF(op->ob_ref, obj);
return 0;
}

static PyGetSetDef cell_getsetlist[] = {
{"cell_contents", (getter)cell_get_contents, NULL},
{"cell_contents", (getter)cell_get_contents,
(setter)cell_set_contents, NULL},
{NULL} /* sentinel */
};

Expand Down