1

Whenever I try to load a pickled object, I get this error:

I'm sorry, but an uncaught exception occurred.

While running game code:
  File "renpy/common/00action_file.rpy", line 328, in __call__
    renpy.load(fn)
TypeError: __init__() takes exactly 4 arguments (1 given)

-- Full Traceback ------------------------------------------------------------

Full traceback:
  File "renpy/common/_layout/screen_main_menu.rpym", line 29, in script
    $ ui.interact()
  File "/home/digiholic/workspace/SummonerSweetheart-0.9-all/renpy/ast.py", line 785, in execute
    renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
  File "/home/digiholic/workspace/SummonerSweetheart-0.9-all/renpy/python.py", line 1382, in py_exec_bytecode
    exec bytecode in globals, locals
  File "renpy/common/_layout/screen_main_menu.rpym", line 29, in <module>
    $ ui.interact()
  File "/home/digiholic/workspace/SummonerSweetheart-0.9-all/renpy/ui.py", line 247, in interact
    rv = renpy.game.interface.interact(roll_forward=roll_forward, **kwargs)
  File "/home/digiholic/workspace/SummonerSweetheart-0.9-all/renpy/display/core.py", line 2149, in interact
    repeat, rv = self.interact_core(preloads=preloads, **kwargs)
  File "/home/digiholic/workspace/SummonerSweetheart-0.9-all/renpy/display/core.py", line 2750, in interact_core
    rv = root_widget.event(ev, x, y, 0)
  File "/home/digiholic/workspace/SummonerSweetheart-0.9-all/renpy/display/layout.py", line 846, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "/home/digiholic/workspace/SummonerSweetheart-0.9-all/renpy/display/layout.py", line 846, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "/home/digiholic/workspace/SummonerSweetheart-0.9-all/renpy/display/layout.py", line 846, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "/home/digiholic/workspace/SummonerSweetheart-0.9-all/renpy/display/screen.py", line 626, in event
    rv = self.child.event(ev, x, y, st)
  File "/home/digiholic/workspace/SummonerSweetheart-0.9-all/renpy/display/layout.py", line 846, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "/home/digiholic/workspace/SummonerSweetheart-0.9-all/renpy/display/layout.py", line 846, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "/home/digiholic/workspace/SummonerSweetheart-0.9-all/renpy/display/layout.py", line 846, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "/home/digiholic/workspace/SummonerSweetheart-0.9-all/renpy/display/behavior.py", line 762, in event
    return handle_click(self.clicked)
  File "/home/digiholic/workspace/SummonerSweetheart-0.9-all/renpy/display/behavior.py", line 705, in handle_click
    rv = run(action)
  File "/home/digiholic/workspace/SummonerSweetheart-0.9-all/renpy/display/behavior.py", line 274, in run
    return var(*args, **kwargs)
  File "renpy/common/00action_file.rpy", line 328, in __call__
    renpy.load(fn)
  File "/home/digiholic/workspace/SummonerSweetheart-0.9-all/renpy/loadsave.py", line 573, in load
    roots, log = loads(location.load(filename))
  File "/home/digiholic/workspace/SummonerSweetheart-0.9-all/renpy/loadsave.py", line 51, in loads
    return pickle.loads(s)
  File "/home/tom/ab/x64lucid-deps/install/lib/python2.7/pickle.py", line 1382, in loads
  File "/home/tom/ab/x64lucid-deps/install/lib/python2.7/pickle.py", line 858, in load
  File "/home/tom/ab/x64lucid-deps/install/lib/python2.7/pickle.py", line 1133, in load_reduce
TypeError: __init__() takes exactly 4 arguments (1 given)

Linux-3.11.0-26-generic-x86_64-with-debian-wheezy-sid
Ren'Py 6.18.3.761
Summoner Sweetheart 0.9

I'm currently using Renpy, a pygame framework. The game loads and saves fine, until I hit a point where it loads an external object I've coded. After that object loads, pickle saves the game state fine, but can't load it, giving me the error above.

I know there's not too much to go on, but this error is so out of my league that I don't even know where the problem code could be.

Matthew Fournier
  • 1,077
  • 2
  • 17
  • 32

2 Answers2

1

You aren't using __reduce__ correctly. See here: https://docs.python.org/2/library/pickle.html#object.reduce. For classes, you can return a tuple of the class and the args you want to pass to __init__. See here https://github.com/uqfoundation/mystic/blob/6bfbc46f9094f96deae020074d7bdad2a43d91d6/mystic/monitors.py#L298 for an example. You can additionally use a __setstate__ method, for more complex behavior for classes.

Minimal example:

>>> class Foo(object):
...   def __init__(self, x, y, z):
...     self.x = x
...     self.y = y
...     self.z = z
...   def __reduce__(self):
...     return (self.__class__, (self.x, self.y, self.z))
...   f = lambda x:x
... 
>>> f = Foo(1,2,3)
>>> 
>>> import pickle
>>> _f = pickle.loads(pickle.dumps(f))
>>> _f.x, _f.y, _f.z
(1, 2, 3)
>>> 

However, if you have a lot of unpicklable items, often the easiest thing to do is to use a better serializer, like dill (see here: https://github.com/uqfoundation/dill.

Mike McKerns
  • 33,715
  • 8
  • 119
  • 139
  • I reduced every class I have defined and still get an identical error. Is there any way I can get the error to tell me which object's init is failing? – Matthew Fournier Jun 08 '15 at 01:49
  • First, note that you have to supply all arguments with `__reduce__`, and can't use keyword arguments (i.e. `x=4`). Second, you could do the dumb thing, and insert logging/print calls to `__reduce__'/'__init__`/`__setstate__` so you can see what blocks of code are run if you can't otherwise tell from the traceback. – Mike McKerns Jun 08 '15 at 11:18
  • When you say I can't use keyword arguments, does that mean I can't provide them at all, or that I can't give them a default? Like if I have a keyword argument directory = "/", and have that stored as self.directory, can I pass self.directory in the reduce? – Matthew Fournier Jun 08 '15 at 15:44
  • You are passing a tuple `args` that has to match up exactly with the arguments in the `__init__` call of `self.__class__`. In `__reduce__`, try passing all the arguments in the exact order specified by `__init__`, and don't rely on any of the defaults (kwds) being used. – Mike McKerns Jun 08 '15 at 15:51
  • Is `__reduce__` called when it loads the object? I put a print statement in every reduce statement, and I didn't get any of them before the program errored out. – Matthew Fournier Jun 08 '15 at 16:44
  • 1
    No, `__reduce__` is called on `dump`. However, if `__reduce__` returns a tuple as in my answer, then `__init__` is called on `load`. You can check which load works by a print in the `__init__`. – Mike McKerns Jun 08 '15 at 16:51
  • But if it's erroring out because `__init__` isn't getting enough arguments, it won't reach the print statement, right? – Matthew Fournier Jun 08 '15 at 16:56
  • Correct. However, any of the `__reduce__ ` that are formed correctly will. So, you can check which methods are built correctly. – Mike McKerns Jun 08 '15 at 18:26
  • Thanks for this - @Matthew of note that if you picked the file with the wrong arguments to reduce then it will be impossible to unpickle it - took me a couple hours till it dawned me - I correcte dmy reduce and I still kept getting the `TypeError: ('__init__() takes exactly 2 arguments (1 given)', , ())` - the pickle was errr molded – Mr_and_Mrs_D Jan 15 '17 at 15:49
0

I had the same problem. The problem lies in how byte objects work in python.

I dont know where the real problem comes from, but if you yourself print that pickled object as byte string (b'blahblah') and give it directly to pickle.loads(b'blahblah') you get the same error.

Some objects seem not to work this way, they can not be serialized, saved and then deserialized again later (maybe because of their dependencies or something else).

My advice for those who want to use pickle to dump their objects is to simply avoid using it.
It does not worth your time.

  1. Simply save attributes that are necessary and then recreate that object using its constructor from these values again later.
  2. Or you can use an ORM tool to manage this for you.

Hope this saves someone time.