Skip to content

Commit c89dc9d

Browse files
authored
Merge pull request #91 from llllllllll/empty-cell
fix functions with empty cells
2 parents 5eefe28 + d14b24a commit c89dc9d

File tree

2 files changed

+57
-3
lines changed

2 files changed

+57
-3
lines changed

cloudpickle/cloudpickle.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -493,8 +493,9 @@ def extract_func_data(self, func):
493493

494494
# process closure
495495
closure = (
496-
[c.cell_contents for c in func.__closure__]
497-
if func.__closure__ is not None else None
496+
list(map(_get_cell_contents, func.__closure__))
497+
if func.__closure__ is not None
498+
else None
498499
)
499500

500501
# save the dict
@@ -908,6 +909,40 @@ def _gen_ellipsis():
908909
def _gen_not_implemented():
909910
return NotImplemented
910911

912+
913+
def _get_cell_contents(cell):
914+
try:
915+
return cell.cell_contents
916+
except ValueError:
917+
# sentinel used by ``_fill_function`` which will leave the cell empty
918+
return _empty_cell_value
919+
920+
921+
def instance(cls):
922+
"""Create a new instance of a class.
923+
924+
Parameters
925+
----------
926+
cls : type
927+
The class to create an instance of.
928+
929+
Returns
930+
-------
931+
instance : cls
932+
A new instance of ``cls``.
933+
"""
934+
return cls()
935+
936+
937+
@instance
938+
class _empty_cell_value(object):
939+
"""sentinel for empty closures
940+
"""
941+
@classmethod
942+
def __reduce__(cls):
943+
return cls.__name__
944+
945+
911946
def _fill_function(func, globals, defaults, dict, closure_values):
912947
""" Fills in the rest of function data into the skeleton function object
913948
that were created via _make_skel_func().
@@ -919,7 +954,8 @@ def _fill_function(func, globals, defaults, dict, closure_values):
919954
cells = func.__closure__
920955
if cells is not None:
921956
for cell, value in zip(cells, closure_values):
922-
cell_set(cell, value)
957+
if value is not _empty_cell_value:
958+
cell_set(cell, value)
923959

924960
return func
925961

tests/cloudpickle_test.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,24 @@ def f():
167167
msg='g now has closure cells even though f does not',
168168
)
169169

170+
def test_empty_cell_preserved(self):
171+
def f():
172+
if False: # pragma: no cover
173+
cell = None
174+
175+
def g():
176+
cell # NameError, unbound free variable
177+
178+
return g
179+
180+
g1 = f()
181+
with pytest.raises(NameError):
182+
g1()
183+
184+
g2 = pickle_depickle(g1)
185+
with pytest.raises(NameError):
186+
g2()
187+
170188
def test_unhashable_closure(self):
171189
def f():
172190
s = set((1, 2)) # mutable set is unhashable

0 commit comments

Comments
 (0)