From 3ca9d71b188556fded2e112c7e01a34b398a0fba Mon Sep 17 00:00:00 2001 From: Futrell Date: Wed, 9 Mar 2016 17:39:49 -0500 Subject: [PATCH 1/2] fix #56 --- cloudpickle/cloudpickle.py | 23 ++++++++++++++++++++++- tests/cloudpickle_test.py | 12 ++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/cloudpickle/cloudpickle.py b/cloudpickle/cloudpickle.py index 306c8591d..069fac93e 100644 --- a/cloudpickle/cloudpickle.py +++ b/cloudpickle/cloudpickle.py @@ -53,6 +53,7 @@ import itertools import dis import traceback +import inspect if sys.version < '3': from pickle import Pickler @@ -181,7 +182,7 @@ def save_function(self, obj, name=None): if name is None: name = obj.__name__ modname = pickle.whichmodule(obj, name) - # print('which gives %s %s %s' % (modname, obj, name)) + #print('which gives %s %s %s' % (modname, obj, name)) try: themodule = sys.modules[modname] except KeyError: @@ -196,6 +197,26 @@ def save_function(self, obj, name=None): if getattr(themodule, name, None) is obj: return self.save_global(obj, name) + # a builtin_function_or_method which comes in as an attribute of some + # object (e.g., object.__new__, itertools.chain.from_iterable) will end + # up with modname "__main__" and so end up here. But these functions + # have no __code__ attribute in CPython, so the handling for + # user-defined functions below will fail. + # So we pickle them here using save_reduce; have to do it differently + # for different python versions. + if not hasattr(obj, '__code__'): + if PY3: + if sys.version_info < (3, 4): + raise pickle.PicklingError("Can't pickle %r" % obj) + else: + rv = obj.__reduce_ex__(self.proto) + else: + if hasattr(obj, '__self__'): + rv = (getattr, (obj.__self__, name)) + else: + raise pickle.PicklingError("Can't pickle %r" % obj) + return Pickler.save_reduce(self, obj=obj, *rv) + # if func is lambda, def'ed at prompt, is in main, or is nested, then # we'll pickle the actual function object rather than simply saving a # reference (as is done in default pickler), via save_function_tuple. diff --git a/tests/cloudpickle_test.py b/tests/cloudpickle_test.py index e0ff4a2db..ec19e1fa4 100644 --- a/tests/cloudpickle_test.py +++ b/tests/cloudpickle_test.py @@ -5,6 +5,7 @@ import pickle import sys import functools +import itertools import platform import textwrap @@ -291,5 +292,16 @@ def test_Ellipsis(self): def test_NotImplemented(self): self.assertEqual(NotImplemented, pickle_depickle(NotImplemented)) + @pytest.mark.skipif((3, 0) < sys.version_info < (3, 4), + reason="fails due to pickle behavior in Python 3.0-3.3") + def test_builtin_function_without_module(self): + on = object.__new__ + on_depickled = pickle_depickle(on) + self.assertEqual(type(on_depickled(object)), type(object())) + + fi = itertools.chain.from_iterable + fi_depickled = pickle_depickle(fi) + self.assertEqual(list(fi([[1, 2], [3, 4]])), [1, 2, 3, 4]) + if __name__ == '__main__': unittest.main() From 62027de3b76552fc2294e04b2e89c186b9270238 Mon Sep 17 00:00:00 2001 From: Futrell Date: Wed, 9 Mar 2016 17:41:26 -0500 Subject: [PATCH 2/2] remove useless import --- cloudpickle/cloudpickle.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cloudpickle/cloudpickle.py b/cloudpickle/cloudpickle.py index 069fac93e..6e6b0793b 100644 --- a/cloudpickle/cloudpickle.py +++ b/cloudpickle/cloudpickle.py @@ -53,7 +53,6 @@ import itertools import dis import traceback -import inspect if sys.version < '3': from pickle import Pickler @@ -182,7 +181,7 @@ def save_function(self, obj, name=None): if name is None: name = obj.__name__ modname = pickle.whichmodule(obj, name) - #print('which gives %s %s %s' % (modname, obj, name)) + # print('which gives %s %s %s' % (modname, obj, name)) try: themodule = sys.modules[modname] except KeyError: