Skip to content

RecursionError for local functions, referencing themselves. #399

@ruro

Description

@ruro

Consider the following:

def outer():
    def inner():
        inner
    return inner

Attempting to dump the inner function (dill.dumps(outer())) results in RecursionError: maximum recursion depth exceeded while calling a Python object. Note, that the third line in the example is not a call to inner, but just a reference.

Python 3.9.1
dill 0.3.3
Stacktrace
/usr/lib/python3.9/site-packages/dill/_dill.py in dumps(obj, protocol, byref, fmode, recurse, **kwds)
    271     """pickle an object to a string"""
    272     file = StringIO()
--> 273     dump(obj, file, protocol, byref, fmode, recurse, **kwds)#, strictio)
    274     return file.getvalue()
    275 

/usr/lib/python3.9/site-packages/dill/_dill.py in dump(obj, file, protocol, byref, fmode, recurse, **kwds)
    265     _kwds = kwds.copy()
    266     _kwds.update(dict(byref=byref, fmode=fmode, recurse=recurse))
--> 267     Pickler(file, protocol, **_kwds).dump(obj)
    268     return
    269 

/usr/lib/python3.9/site-packages/dill/_dill.py in dump(self, obj)
    452             raise PicklingError(msg)
    453         else:
--> 454             StockPickler.dump(self, obj)
    455         stack.clear()  # clear record of 'recursion-sensitive' pickled objects
    456         return

/usr/lib/python3.9/pickle.py in dump(self, obj)
    485         if self.proto >= 4:
    486             self.framer.start_framing()
--> 487         self.save(obj)
    488         self.write(STOP)
    489         self.framer.end_framing()

/usr/lib/python3.9/pickle.py in save(self, obj, save_persistent_id)
    558             f = self.dispatch.get(t)
    559             if f is not None:
--> 560                 f(self, obj)  # Call unbound method with explicit self
    561                 return
    562 

/usr/lib/python3.9/site-packages/dill/_dill.py in save_function(pickler, obj)
   1442             if _memo: pickler._recurse = False
   1443             fkwdefaults = getattr(obj, '__kwdefaults__', None)
-> 1444             pickler.save_reduce(_create_function, (obj.__code__,
   1445                                 globs, obj.__name__,
   1446                                 obj.__defaults__, obj.__closure__,

/usr/lib/python3.9/pickle.py in save_reduce(self, func, args, state, listitems, dictitems, state_setter, obj)
    690         else:
    691             save(func)
--> 692             save(args)
    693             write(REDUCE)
    694 

/usr/lib/python3.9/pickle.py in save(self, obj, save_persistent_id)
    558             f = self.dispatch.get(t)
    559             if f is not None:
--> 560                 f(self, obj)  # Call unbound method with explicit self
    561                 return
    562 

/usr/lib/python3.9/pickle.py in save_tuple(self, obj)
    899         write(MARK)
    900         for element in obj:
--> 901             save(element)
    902 
    903         if id(obj) in memo:

/usr/lib/python3.9/pickle.py in save(self, obj, save_persistent_id)
    558             f = self.dispatch.get(t)
    559             if f is not None:
--> 560                 f(self, obj)  # Call unbound method with explicit self
    561                 return
    562 

/usr/lib/python3.9/pickle.py in save_tuple(self, obj)
    884         if n <= 3 and self.proto >= 2:
    885             for element in obj:
--> 886                 save(element)
    887             # Subtle.  Same as in the big comment below.
    888             if id(obj) in memo:

/usr/lib/python3.9/pickle.py in save(self, obj, save_persistent_id)
    558             f = self.dispatch.get(t)
    559             if f is not None:
--> 560                 f(self, obj)  # Call unbound method with explicit self
    561                 return
    562 

/usr/lib/python3.9/site-packages/dill/_dill.py in save_cell(pickler, obj)
   1176     log.info("Ce: %s" % obj)
   1177     f = obj.cell_contents
-> 1178     pickler.save_reduce(_create_cell, (f,), obj=obj)
   1179     log.info("# Ce")
   1180     return

/usr/lib/python3.9/pickle.py in save_reduce(self, func, args, state, listitems, dictitems, state_setter, obj)
    690         else:
    691             save(func)
--> 692             save(args)
    693             write(REDUCE)
    694 

/usr/lib/python3.9/pickle.py in save(self, obj, save_persistent_id)
    558             f = self.dispatch.get(t)
    559             if f is not None:
--> 560                 f(self, obj)  # Call unbound method with explicit self
    561                 return
    562 

/usr/lib/python3.9/pickle.py in save_tuple(self, obj)
    884         if n <= 3 and self.proto >= 2:
    885             for element in obj:
--> 886                 save(element)
    887             # Subtle.  Same as in the big comment below.
    888             if id(obj) in memo:

... last 12 frames repeated, from the frame below ...

/usr/lib/python3.9/pickle.py in save(self, obj, save_persistent_id)
    558             f = self.dispatch.get(t)
    559             if f is not None:
--> 560                 f(self, obj)  # Call unbound method with explicit self
    561                 return
    562 

RecursionError: maximum recursion depth exceeded while calling a Python object

A few variations/use-cases for this kind of self-referencing:

def make_function_with_self_recursion(a=0, b=1):
    def fib(n):
        if n == 0:
            return a
        if n == 1:
            return b
        return fib(n-1) + fib(n-2)

    return fib


def make_function_with_state():
    def _fun():
        _fun.state += 1
        return _fun.state

    _fun.state = 0
    return _fun

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions