0

I want to save the state of widgets like layouts, buttons etc. and found pickle was recommended for this. However, the error is "TypeError: no default reduce due to non-trivial cinit" which doesn't explain anything to me and I can't find any solution online.

I don't mind other solutions to save, but I tried json and the storage module and can't make it work either.

What can I do here?

Thanks for reading

from kivy.properties import ObjectProperty, StringProperty, NumericProperty, BoundedNumericProperty, ListProperty, BooleanProperty
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.graphics import Rectangle, Color
from kivy.core.text import LabelBase
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.pagelayout import PageLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.app import App
import numpy as np
import random
from random import randrange
from kivy.storage.jsonstore import JsonStore
import pickle
import json

class SaveButton(Button):
    def __init__(self, **kwargs):
        super(SaveButton, self).__init__(**kwargs)
        self.background_color = (20/255, 20/255, 20/255, 1)
        self.size_hint = 0.1, 0.05
        self.text = "Save game"
        self.pos_hint = {'center_x': .5, 'center_y': .05}
        self.font_size = "22sp"



class TestScreen(Screen):
    def __init__(self, **kwargs):
        super(TestScreen, self).__init__(**kwargs)
        self.testlayout = TestLayout()

        self.add_widget(self.testlayout)
        
        self.savebutton = SaveButton()
        self.add_widget(self.savebutton)
        self.savebutton.bind(on_release=self.save_game)
        
    def save_game(self, *args):
        saved_game = open("saved game.py", "w")
        pickle.dump(self.testlayout, saved_game)
        
class TestLayout(FloatLayout):
    def __init__(self, **kwargs):
        super(TestLayout, self).__init__(**kwargs)
        self.testbutton = TestButton()
        self.add_widget(self.testbutton)
        self.testbutton.bind(on_release=self.example_change)
    
    def example_change(self, *args):
        self.testbutton.pos_hint = {'center_x':.5, 'center_y':.8}
        
class TestButton(Button):
    def __init__(self, **kwargs):
        super(TestButton, self).__init__(**kwargs)
        self.size_hint = 0.05, 0.1
        self.pos_hint = {'center_x':.5, 'center_y':.5}
        
class Manager(ScreenManager):
    def __init__(self, **kwargs):
        super(Manager, self).__init__(**kwargs)
        self.testscreen = TestScreen()
        self.testscreen.name = "testscreen"
        self.add_widget(self.testscreen)

class DemoApp(App):
    def build(self):
        self.manager = Manager()
        return self.manager

DemoApp().run()
Lyghtning
  • 5
  • 3

1 Answers1

0

Pickle is often recommended because it's "easy", but a lot of the time it's actually a bad idea because although you're not writing many lines of code the actual result isn't very good - pickle files are large, opaque, unsafe (which may or may not matter), and unreliable as they can only be loaded again if the loading environment is sufficiently similar to the one where the pickle was generated.

Further, pickle can't necessarily serialise everything and that's the problem you have here: the error is essentially saying that the kivy classes do something that the pickle can't store. I'm not sure if it might be possible to work around this with a different pickle protocol, but I'd recommend not using pickle at all.

What you probably want instead is to write a function that takes in your widget and stores all the important detail about it (e.g. the value of every property you care about), then another that takes in that detail and reconstructs a new widget with all the properties set to that value. This is more up front work but ends up clearer and more reliable. Once you're generating the information you need to reconstruct the widget (e.g. as a dictionary) you can save that with e.g. json, but you can't save the widget itself as json because json doesn't support anything but a specific series of object types.

Edit: That isn't to say pickle is universally bad, it's good for e.g. passing objects between python multiprocesses because then you don't have to worry about different python environments or the lack of security.

inclement
  • 29,124
  • 4
  • 48
  • 60
  • Thanks for the answer. Do you have a code example, since I already tried json and can't make it work either? – Lyghtning Apr 25 '22 at 07:57