Skip to content
Closed
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
9 changes: 9 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
0.7.0
=====

- Add a switch in `cloudpickle.dump` speficying if variables present in the
written pickle string should override colluding values present in their new
namespace at unpickling time.
([issue #214](https://github.com/cloudpipe/cloudpickle/issues/214))


0.6.1
=====

Expand Down
56 changes: 47 additions & 9 deletions cloudpickle/cloudpickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,16 +266,46 @@ def _walk_global_ops(code):


class CloudPickler(Pickler):
"""Pickler with extended pickling abilities.

Parameters
----------
file :

protocol : int

override_globals : bool
Functions belonging to the same module (the __main__ or a dynamic
module) share the same global variables. Using cloudpickle, they will
share those global variables in the process they are unpickled in as
well. However, the state of those global variables may differ in the
pickled functions, for example if one global variable was mutated
between two calls to pickle.dump. override_globals is a
switch that allows two different behaviors:

* If override_globals=True, the global variables of the
module containing this function will be overridden in the process
which load the pickled object with the values of these variables at
the pickling time.
* If override_globals=False, the global variables of the
module containing the definition of the nested or dynamically defined
function will not be overridden in the process loading the pickled
object. If the global variable is not declared, it will be
initialized with the value at pickling time.

"""

dispatch = Pickler.dispatch.copy()

def __init__(self, file, protocol=None):
def __init__(self, file, protocol=None, override_globals=True):
if protocol is None:
protocol = DEFAULT_PROTOCOL
Pickler.__init__(self, file, protocol=protocol)
# map ids to dictionary. used to ensure that functions can share global env
self.globals_ref = {}

self.override_globals = override_globals

def dump(self, obj):
self.inject_addons()
try:
Expand Down Expand Up @@ -590,6 +620,7 @@ def save_function_tuple(self, func):
'module': func.__module__,
'name': func.__name__,
'doc': func.__doc__,
'override_globals': self.override_globals
}
if hasattr(func, '__annotations__') and sys.version_info >= (3, 7):
state['annotations'] = func.__annotations__
Expand Down Expand Up @@ -921,7 +952,7 @@ def _rebuild_tornado_coroutine(func):

# Shorthands for legacy support

def dump(obj, file, protocol=None):
def dump(obj, file, protocol=None, override_globals=True):
"""Serialize obj as bytes streamed into file

protocol defaults to cloudpickle.DEFAULT_PROTOCOL which is an alias to
Expand All @@ -931,10 +962,11 @@ def dump(obj, file, protocol=None):
Set protocol=pickle.DEFAULT_PROTOCOL instead if you need to ensure
compatibility with older versions of Python.
"""
CloudPickler(file, protocol=protocol).dump(obj)
CloudPickler(file, protocol=protocol,
override_globals=override_globals).dump(obj)


def dumps(obj, protocol=None):
def dumps(obj, protocol=None, override_globals=True):
"""Serialize obj as a string of bytes allocated in memory

protocol defaults to cloudpickle.DEFAULT_PROTOCOL which is an alias to
Expand All @@ -946,7 +978,8 @@ def dumps(obj, protocol=None):
"""
file = StringIO()
try:
cp = CloudPickler(file, protocol=protocol)
cp = CloudPickler(file, protocol=protocol,
override_globals=override_globals)
cp.dump(obj)
return file.getvalue()
finally:
Expand Down Expand Up @@ -1078,10 +1111,15 @@ def _fill_function(*args):
else:
raise ValueError('Unexpected _fill_value arguments: %r' % (args,))

# Only set global variables that do not exist.
for k, v in state['globals'].items():
if k not in func.__globals__:
func.__globals__[k] = v
# This updates the variables of func's module using func's globals from the
# time at which it was pickled.
if state.get('override_globals', True):
func.__globals__.update(state['globals'])
else:
for k, v in state['globals'].items():
Copy link
Contributor

Choose a reason for hiding this comment

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

I would re-add the original comment for this block of the condition:

# Only set global variables that do not exist.

# Only set global variables that do not exist.
if k not in func.__globals__:
func.__globals__[k] = v

func.__defaults__ = state['defaults']
func.__dict__ = state['dict']
Expand Down
Loading