Skip to content

Commit

Permalink
Bring back old method pickling function (#511)
Browse files Browse the repository at this point in the history
This is a very strange use case of functions and methods that Python's 
pickle package doesn't handle correctly (#510). This case used to work 
in pre-0.3.5 dill, so let's bring back the old dill implementation.

The comments are irrelevant in Python 3.
  • Loading branch information
anivegesana committed Jun 16, 2022
1 parent dc9a1b9 commit 408b8de
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 20 deletions.
64 changes: 44 additions & 20 deletions dill/_dill.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
16 changes: 16 additions & 0 deletions tests/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -104,3 +119,4 @@ def test_functions():

if __name__ == '__main__':
test_functions()
test_issue_510()

0 comments on commit 408b8de

Please sign in to comment.