Skip to content

Commit

Permalink
Preserve func.__qualname__ when defined
Browse files Browse the repository at this point in the history
  • Loading branch information
ogrisel authored and rgbkrk committed Nov 8, 2017
1 parent 7d8c670 commit 14b38a3
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 20 deletions.
57 changes: 37 additions & 20 deletions cloudpickle/cloudpickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,11 +529,16 @@ def save_function_tuple(self, func):
self.memoize(func)

# save the rest of the func data needed by _fill_function
save(f_globals)
save(defaults)
save(dct)
save(func.__module__)
save(closure_values)
state = {
'globals': f_globals,
'defaults': defaults,
'dict': dct,
'module': func.__module__,
'closure_values': closure_values,
}
if hasattr(func, '__qualname__'):
state['qualname'] = func.__qualname__
save(state)
write(pickle.TUPLE)
write(pickle.REDUCE) # applies _fill_function on the tuple

Expand Down Expand Up @@ -944,27 +949,39 @@ def __reduce__(cls):


def _fill_function(*args):
if len(args) == 5:
"""Fills in the rest of function data into the skeleton function object
The skeleton itself is create by _make_skel_func().
"""
if len(args) == 2:
func = args[0]
state = args[1]
elif len(args) == 5:
# Backwards compat for cloudpickle v0.4.0, after which the `module`
# argument was introduced
updated_args = args[:-1] + (None, args[-1],)
return _fill_function_internal(*updated_args)
# argument was introduced
func = args[0]
keys = ['globals', 'defaults', 'dict', 'closure_values']
state = dict(zip(keys, args[1:]))
state['module'] = None
elif len(args) == 6:
# Backwards compat for cloudpickle v0.4.1, after which the function
# state was passed as a dict to the _fill_function it-self.
func = args[0]
keys = ['globals', 'defaults', 'dict', 'module', 'closure_values']
state = dict(zip(keys, args[1:]))
else:
return _fill_function_internal(*args)
raise ValueError('Unexpected _fill_value arguments: %r' % (args,))


def _fill_function_internal(func, globals, defaults, dict, module, closure_values):
""" Fills in the rest of function data into the skeleton function object
that were created via _make_skel_func().
"""
func.__globals__.update(globals)
func.__defaults__ = defaults
func.__dict__ = dict
func.__module__ = module
func.__globals__.update(state['globals'])
func.__defaults__ = state['defaults']
func.__dict__ = state['dict']
func.__module__ = state['module']
if 'qualname' in state:
func.__qualname__ = state['qualname']

cells = func.__closure__
if cells is not None:
for cell, value in zip(cells, closure_values):
for cell, value in zip(cells, state['closure_values']):
if value is not _empty_cell_value:
cell_set(cell, value)

Expand Down
12 changes: 12 additions & 0 deletions tests/cloudpickle_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,18 @@ def test_function_module_name(self):
func = lambda x: x
self.assertEqual(pickle_depickle(func).__module__, func.__module__)

def test_function_qualname(self):
def func(x):
return x
# Default __qualname__ attribute (Python 3 only)
if hasattr(func, '__qualname__'):
self.assertEqual(pickle_depickle(func).__qualname__,
func.__qualname__)

# Mutated __qualname__ attribute
func.__qualname__ = '<modifiedlambda>'
self.assertEqual(pickle_depickle(func).__qualname__, func.__qualname__)

def test_namedtuple(self):

MyTuple = collections.namedtuple('MyTuple', ['a', 'b', 'c'])
Expand Down

0 comments on commit 14b38a3

Please sign in to comment.