diff --git a/cloudpickle/cloudpickle.py b/cloudpickle/cloudpickle.py index 306c8591d..6e6b0793b 100644 --- a/cloudpickle/cloudpickle.py +++ b/cloudpickle/cloudpickle.py @@ -196,6 +196,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()