Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
42 changes: 39 additions & 3 deletions cloudpickle/cloudpickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -896,6 +897,40 @@ 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


def instance(cls):

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.

Ha :-)

@rgbkrk rgbkrk Jun 2, 2017

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.

Should this just go back to @object.__new__? 😄

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.

No, it's ok to me :-)

"""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
"""
@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().
Expand All @@ -907,7 +942,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

Expand Down
18 changes: 18 additions & 0 deletions tests/cloudpickle_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down