diff --git a/dill/_dill.py b/dill/_dill.py index 89f2c464..becd1b6a 100644 --- a/dill/_dill.py +++ b/dill/_dill.py @@ -1767,15 +1767,29 @@ def save_builtin_method(pickler, obj): log.info("# B2") return - @register(MethodType) #FIXME: fails for 'hidden' or 'name-mangled' classes - def save_instancemethod0(pickler, obj):# example: cStringIO.StringI - log.info("Me: %s" % obj) #XXX: obj.__dict__ handled elsewhere? - if PY3: - pickler.save_reduce(MethodType, (obj.__func__, obj.__self__), obj=obj) - else: - pickler.save_reduce(MethodType, (obj.im_func, obj.im_self, - obj.im_class), obj=obj) - log.info("# Me") +if IS_PYPY: + @register(MethodType) + def save_instancemethod0(pickler, obj): + code = getattr(obj.__func__, '__code__', None) + if code is not None and type(code) is not CodeType \ + and getattr(obj.__self__, obj.__name__) == obj: + # Some PyPy builtin functions have no module name + log.info("Me2: %s" % obj) + # TODO: verify that this works for all PyPy builtin methods + pickler.save_reduce(getattr, (obj.__self__, obj.__name__), obj=obj) + log.info("# Me2") + return + + log.info("Me1: %s" % obj) + pickler.save_reduce(MethodType, (obj.__func__, obj.__self__), obj=obj) + log.info("# Me1") + return +else: + @register(MethodType) + def save_instancemethod0(pickler, obj): + log.info("Me1: %s" % obj) + pickler.save_reduce(MethodType, (obj.__func__, obj.__self__), obj=obj) + log.info("# Me1") return if sys.hexversion >= 0x20500f0: @@ -1801,17 +1815,6 @@ def save_wrapper_descriptor(pickler, obj): log.info("# Wr") return - @register(MethodWrapperType) - def save_instancemethod(pickler, obj): - log.info("Mw: %s" % obj) - if IS_PYPY2 and obj.__self__ is None and obj.im_class: - # Can be a class method in PYPY2 if __self__ is none - pickler.save_reduce(getattr, (obj.im_class, obj.__name__), obj=obj) - return - pickler.save_reduce(getattr, (obj.__self__, obj.__name__), obj=obj) - log.info("# Mw") - return - elif not IS_PYPY: @register(MethodDescriptorType) @register(WrapperDescriptorType) @@ -2139,6 +2142,27 @@ def save_classmethod(pickler, obj): @register(FunctionType) def save_function(pickler, obj): if not _locate_function(obj, pickler): + if type(obj.__code__) is not CodeType: + # Some PyPy builtin functions have no module name, and thus are not + # able to be located + module_name = getattr(obj, '__module__', None) + if module_name is None: + module_name = __builtin__.__name__ + module = _import_module(module_name, safe=True) + _pypy_builtin = False + try: + found, _ = _getattribute(module, obj.__qualname__) + if getattr(found, '__func__', None) is obj: + _pypy_builtin = True + except: + pass + + if _pypy_builtin: + log.info("F3: %s" % obj) + pickler.save_reduce(getattr, (found, '__func__'), obj=obj) + log.info("# F3") + return + log.info("F1: %s" % obj) _recurse = getattr(pickler, '_recurse', None) _postproc = getattr(pickler, '_postproc', None) diff --git a/tests/test_functions.py b/tests/test_functions.py index ec9670e2..a86cbde0 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -54,6 +54,21 @@ def function_with_unassigned_variable(): return (lambda: value) +def test_issue_510(): + # A very bizzare use of functions and methods that pickle doesn't get + # correctly for odd reasons. + class Foo: + def __init__(self): + def f2(self): + return self + self.f2 = f2.__get__(self) + + import dill, pickletools + f = Foo() + f1 = dill.copy(f) + assert f1.f2() is f1 + + def test_functions(): dumped_func_a = dill.dumps(function_a) assert dill.loads(dumped_func_a)(0) == 0 @@ -104,3 +119,4 @@ def test_functions(): if __name__ == '__main__': test_functions() + test_issue_510()