From 2f4c07d9684d1a7f988ac18696ce9d1daa77b071 Mon Sep 17 00:00:00 2001 From: Joe Jevnik Date: Tue, 30 May 2017 20:18:57 -0400 Subject: [PATCH 1/2] fix functions with empty cells --- cloudpickle/cloudpickle.py | 26 +++++++++++++++++++++++--- tests/cloudpickle_test.py | 18 ++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/cloudpickle/cloudpickle.py b/cloudpickle/cloudpickle.py index 030d44a3f..d384b3fd4 100644 --- a/cloudpickle/cloudpickle.py +++ b/cloudpickle/cloudpickle.py @@ -492,8 +492,9 @@ def extract_func_data(self, func): # process closure closure = ( - [c.cell_contents for c in func.__closure__] - if func.__closure__ is not None else None + list(map(_get_cell_contents, func.__closure__)) + if func.__closure__ is not None + else None ) # save the dict @@ -896,6 +897,24 @@ def _gen_ellipsis(): def _gen_not_implemented(): return NotImplemented + +def _get_cell_contents(cell): + try: + return cell.cell_contents + except ValueError: + # sentinel used by ``_fill_function`` which will leave the cell empty + return _empty_cell_value + + +@object.__new__ +class _empty_cell_value(object): + """sentinel for empty closures + """ + @classmethod + def __reduce__(cls): + return cls.__name__ + + def _fill_function(func, globals, defaults, dict, closure_values): """ Fills in the rest of function data into the skeleton function object that were created via _make_skel_func(). @@ -907,7 +926,8 @@ def _fill_function(func, globals, defaults, dict, closure_values): cells = func.__closure__ if cells is not None: for cell, value in zip(cells, closure_values): - cell_set(cell, value) + if value is not _empty_cell_value: + cell_set(cell, value) return func diff --git a/tests/cloudpickle_test.py b/tests/cloudpickle_test.py index 19f1faf1f..f27cb7cba 100644 --- a/tests/cloudpickle_test.py +++ b/tests/cloudpickle_test.py @@ -167,6 +167,24 @@ def f(): msg='g now has closure cells even though f does not', ) + def test_empty_cell_preserved(self): + def f(): + if False: # pragma: no cover + cell = None + + def g(): + cell # NameError, unbound free variable + + return g + + g1 = f() + with pytest.raises(NameError): + g1() + + g2 = pickle_depickle(g1) + with pytest.raises(NameError): + g2() + def test_unhashable_closure(self): def f(): s = set((1, 2)) # mutable set is unhashable From d14b24a7d133c943b023c8851a3fab7eec9f97b5 Mon Sep 17 00:00:00 2001 From: Joe Jevnik Date: Thu, 1 Jun 2017 17:06:40 -0400 Subject: [PATCH 2/2] name the instance decorator --- cloudpickle/cloudpickle.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/cloudpickle/cloudpickle.py b/cloudpickle/cloudpickle.py index d384b3fd4..759cdb22a 100644 --- a/cloudpickle/cloudpickle.py +++ b/cloudpickle/cloudpickle.py @@ -906,7 +906,23 @@ def _get_cell_contents(cell): return _empty_cell_value -@object.__new__ +def instance(cls): + """Create a new instance of a class. + + Parameters + ---------- + cls : type + The class to create an instance of. + + Returns + ------- + instance : cls + A new instance of ``cls``. + """ + return cls() + + +@instance class _empty_cell_value(object): """sentinel for empty closures """