diff --git a/dill/_dill.py b/dill/_dill.py index cb17cae4..acfe009f 100644 --- a/dill/_dill.py +++ b/dill/_dill.py @@ -706,9 +706,14 @@ def _create_function(fcode, fglobals, fname=None, fdefaults=None, fclosure=None, fdict=None, fkwdefaults=None): # same as FunctionType, but enable passing __dict__ to new function, # __dict__ is the storehouse for attributes added after function creation - if fdict is None: fdict = dict() func = FunctionType(fcode, fglobals or dict(), fname, fdefaults, fclosure) - func.__dict__.update(fdict) #XXX: better copy? option to copy? + if fdict is not None: + func.__dict__.update(fdict) #XXX: better copy? option to copy? + elif IS_PYPY2: + # __reduce__ crashes in PyPy2 + # __setstate__ is not used in any PyPy2 code and is removed in PyPy3 + # so is likely an artifact + func.__setstate__ = None if fkwdefaults is not None: func.__kwdefaults__ = fkwdefaults # 'recurse' only stores referenced modules/objects in fglobals, @@ -1604,8 +1609,9 @@ def save_type(pickler, obj, postproc_list=None): log.info("# T1") elif issubclass(obj, tuple) and all([hasattr(obj, attr) for attr in ('_fields','_asdict','_make','_replace')]): # special case: namedtuples + # TODO: namedtuple can be extended. This doesn't cover it. log.info("T6: %s" % obj) - pickler.save_reduce(_create_namedtuple, (getattr(obj, "__qualname__", obj.__name__), obj._fields, obj.__module__), obj=obj) + pickler.save_reduce(_create_namedtuple, (obj.__name__, obj._fields, obj.__module__), obj=obj) log.info("# T6") return @@ -1748,17 +1754,30 @@ def save_function(pickler, obj): postproc_list.append((dict.update, (globs, globs_copy))) if PY3: - #NOTE: workaround for 'super' (see issue #75) removed in #443 - fkwdefaults = getattr(obj, '__kwdefaults__', None) + state_dict = {} + for fattrname in ('__kwdefaults__', '__annotations__'): + fattr = getattr(obj, fattrname, None) + if fattr is not None: + state_dict[fattrname] = fattr + if obj.__qualname__ != obj.__name__: + state_dict['__qualname__'] = obj.__qualname__ + + state = obj.__dict__ + if type(state) is not dict: + state_dict['__dict__'] = state + state = None + if state_dict: + state = state, state_dict + _save_with_postproc(pickler, (_create_function, ( obj.__code__, globs, obj.__name__, obj.__defaults__, - obj.__closure__, obj.__dict__, fkwdefaults - )), obj=obj, postproc_list=postproc_list) + obj.__closure__ + ), state), obj=obj, postproc_list=postproc_list) else: _save_with_postproc(pickler, (_create_function, ( obj.func_code, globs, obj.func_name, obj.func_defaults, - obj.func_closure, obj.__dict__ - )), obj=obj, postproc_list=postproc_list) + obj.func_closure + ), obj.__dict__), obj=obj, postproc_list=postproc_list) log.info("# F1") else: log.info("F2: %s" % obj) diff --git a/tests/test_classdef.py b/tests/test_classdef.py index 5f07be5e..0acf0223 100644 --- a/tests/test_classdef.py +++ b/tests/test_classdef.py @@ -114,6 +114,13 @@ def test_namedtuple(): assert Bad._fields == dill.loads(dill.dumps(Bad))._fields assert tuple(Badi) == tuple(dill.loads(dill.dumps(Badi))) + class A: + class B(namedtuple("B", ["one", "two"])): + pass + + a = A() + assert dill.copy(a) + def test_dtype(): try: import numpy as np